En I/O 2019, anunciamos que Android priorizará Kotlin. Sin embargo, algunos desarrolladores mencionaron que todavía no saben cómo proceder. Puede sonar aterrador tener que empezar a escribir código Kotlin, especialmente si nadie de tu equipo está familiarizado con él.
Los que usamos generadores de perfiles de Android Studio tomamos un enfoque gradual. El primer paso fue exigir que todas nuestras pruebas unitarias se escribieran en Kotlin. Debido a que estas clases estaban aisladas del código de producción, podíamos abordar cualquier error inicial que hacíamos.
Desde ese entonces, he recopilado la siguiente guía de problemas comunes que nuestro equipo experimentó durante varias revisiones de código. Incluiré los detalles a continuación con la esperanza de que ayude a otros miembros de la amplia comunidad de Android.
Nota: Esta publicación está dirigida a las personas que recién comienzan a usar Kotlin. Si tú y tu equipo ya escriben código Kotlin de manera efectiva, es posible que esta entrada no incluya información nueva. Sin embargo, si piensas que debería haber incluido algo que omití, ¡deja un comentario!

Acción de IDE: Convertir un archivo Java en uno de Kotlin

Si utilizas Android Studio, la forma más sencilla de empezar a aprender Kotlin es escribir la clase de prueba en Java y, luego, convertirla en Kotlin seleccionando Code → Convert Java File to Kotlin en la barra de menú.
Es posible que esa acción muestre el mensaje "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?". Te recomiendo que elijas "No" para que puedas enfocarte en un solo archivo a la vez.
Si bien esta acción genera código Kotlin, siempre se puede mejorar ese resultado. Las siguientes secciones incluyen sugerencias comunes que hemos recopilado de docenas de revisiones respecto de ese código generado automáticamente. Es obvio que Kotlin es mucho más que lo que se describe a continuación, pero para no desviarnos, esta guía solo se centrará en los temas recurrentes que observamos.

Comparación de lenguajes de alto nivel

Desde un punto de vista de alto nivel, Java y Kotlin son bastante similares. A continuación, se incluye una clase de prueba de estructura escrita en Java y, luego, en Kotlin.
/// Java
public class ExampleTest {
  @Test
  public void testMethod1() throws Exception {}
  @Test
  public void testMethod2() throws Exception {}
}
/// Kotlin
class ExampleTest {
  @Test
  fun testMethod1() {}
  @Test
  fun testMethod2() {}
}
Vale la pena destacar las optimizaciones de Kotlin:
  • Los métodos y las clases son públicos de forma predeterminada.
  • No es necesario declarar explícitamente el tipo de resultado anulado.
  • No hay excepciones verificadas.

Los punto y coma son opcionales

Este es uno de esos cambios que probablemente incomoden al principio. En la práctica, no debes preocuparte demasiado por ello. En este caso, simplemente escribes tu código, y si pones punto y coma por costumbre, el código seguirá compilándose, y el IDE te lo indicará. Solo hay que quitarlos todos antes de enviarlos.
Independientemente de si odias esta opción o no, Java ya quita los punto y coma en algunos lugares, lo que se nota si se compara con C++ (que requiere puntos y coma en más partes).
/// C++
std::thread([this] { this->DoThreadWork(); });
/// Java
new Thread(() -> doThreadWork());
/// Kotlin
Thread { doThreadWork() }

Los tipos se declaran al final

/// Java
int value = 10;
Entry add(String name, String description)
/// Kotlin
var value: Int = 10
fun add(name: String, description: String): Entry
Al igual que los punto y coma opcionales, este cambio es uno que probablemente te resultará muy difícil de aceptar si no estás acostumbrado a él. Es exactamente el orden opuesto a lo que muchos han naturalizado a lo largo de su carrera como programadores.
Sin embargo, la ventaja de esta sintaxis es que facilita la omisión de tipos cuando se pueden inferir. Esto se trata con más detalle en la sección "Omisión de tipos de variables" que aparece más abajo.
Esta sintaxis también pone más énfasis en la variable en sí y no en su tipo. Creo que este orden suena más natural cuando se habla del código en voz alta:
/// Java
int result;     // An integer that is a variable called "result".
/// Kotlin
var result: Int // A variable called "result" that is an integer.
Lo último que voy a decir sobre esta sintaxis es que, por muy incómoda que sea al principio, te acostumbrarás a ella con el paso del tiempo.

Constructores sin el término "new"

Kotlin no requiere la palabra clave new antes de una llamada a un constructor.
/// Java
... = new SomeClass();
/// Kotlin
... = SomeClass()
Al principio, esto puede parecer como si estuvieras perdiendo información crucial, pero no es así, ya que muchas funciones de Java asignan memoria en segundo plano, y probablemente nunca lo hayas notado. Muchas bibliotecas incluso tienen métodos de creación estáticos, como los siguientes ejemplos:
/// Java
Lists.newArrayList();
Así que, en realidad, Kotlin solo hace que esto sea más coherente. Cualquier función puede o no asignar memoria como efecto secundario.
Esto también elimina el patrón en el que se asigna una clase temporal solo para llamar a una función sin asignarla a nada.
/// Java
new Thread(...).start(); // Awkward but valid
/// Kotlin
Thread(...).start()

Mutabilidad e inmutabilidad

Las variables de Java son mutables de forma predeterminada y requieren la palabra clave "final" para que sean inmutables. Por el contrario, Kotlin no la necesita. En cambio, es necesario etiquetar las propiedades con "val" para indicar un valor (inmutable) o "var" para indicar una variable (mutable).
/// Java
private final Project project; // Cannot reassign after init
private Module activeModule;   // Can reassign after init
/// Kotlin
private val project: Project     // Cannot reassign after init
private var activeModule: Module // Can reassign after init
A menudo, en Java encontrarás muchos campos que podrían haber sido definitivos, pero la palabra clave fue omitida (probablemente por accidente, ya que es fácil de olvidar). En Kotlin, debes ser explícito respecto de esta decisión en cada campo que declares. Si no sabes cuál debería ser, márcalo como valor predeterminado y cámbialo a "var" más tarde si se modifican los requisitos.
Por otro lado, en Java, los parámetros de función son siempre mutables y, al igual que los campos, pueden hacerse inmutables mediante el uso de "final". En Kotlin, los parámetros de función son siempre inmutables, es decir, se etiquetan implícitamente como "val".
/// Java
public void log(final String message) { … }
/// Kotlin
fun log(message: String) { … } // "message" is immutable

Nulidad

Kotlin no admite el uso de anotaciones "@NotNull" o "@Nullable". Si un valor puede ser nulo, simplemente debes declarar su tipo con un signo de interrogación.
/// Java
@Nullable Project project;
@NotNull String title;
/// Kotlin
val project: Project?
val title: String
En ciertos casos, si sabes que un valor nullable siempre será not-null, puedes usar el operador "!!" para reivindicarlo.
/// Kotlin
// 'parse' could return null, but this test case always works
val result = parse("123")!!// The following line is not necessary. !! already asserts.
❌ assertThat(result).isNotNull()
Si aplicas "!!" de forma incorrecta, el código podría arrojar un error "NullPointerException". En una prueba de unidad, esto solo causa una falla en la prueba, pero debes tener cuidado si usas esto en el código de producción. De hecho, muchos consideran que incluir "!!" en el código es un indicio de un posible problema (aunque hay suficiente evidencia de que esto amerita su propia entrada de blog).
En una prueba de unidad, es más aceptable afirmar que un caso específico es válido usando el operador "!!", porque si esta suposición deja de ser cierta, fallará la prueba y podrás arreglarla.
Si crees que tiene sentido utilizar el operador "!!", úsalo lo antes posible. Por ejemplo, haz lo siguiente:
/// Kotlin
val result = parse("...")!!
result.doSomething()
result.doSomethingElse()
Pero no hagas esto:
/// Kotlin (auto-generated)
val result = parse("...")
❌ result!!.doSomething()
❌ result!!.doSomethingElse()

Omisión de tipos de variables

En Java, verás un montón de código como este:
/// Java
SomeClass instance1 = new SomeClass();
SomeGeneric<List<String>> instance2 = new SomeGeneric<>();
En Kotlin, esas declaraciones de tipo se consideran redundantes y no es necesario escribirlas dos veces:
/// Kotlin
val instance1 = SomeClass()
val instance2 = SomeGeneric<List<String>>()
"¡Pero espera!" gritas. "¡A veces, sí tenía la intención de declarar esos tipos!". Por ejemplo:
/// Java
BaseClass instance = new ChildClass(); // e.g. List = new ArrayList
Esto se puede hacer en Kotlin usando la siguiente sintaxis:
/// Kotlin
val instance: BaseClass = ChildClass()

Sin excepciones verificadas

A diferencia de Java, Kotlin no requiere que sus métodos declaren qué excepciones lanzan. Ya no hay diferencia entre excepciones verificadas y excepciones del entorno de ejecución.
/// Java
public void readFile() throws IOException { … }
/// Kotlin
fun readFile() { … }
Sin embargo, para permitir que Java pueda llamar al código Kotlin, Kotlin admite la declaración indirecta de excepciones usando la anotación @Throws. La acción Java → Kotlin es segura y siempre incluye esta información.
/// Kotlin (auto-converted from Java)
@Throws(Exception::class)
fun testSomethingImportant() { … }
Pero nunca tendrás que preocuparte de que se llame a tus pruebas de unidad desde una clase Java. Por lo tanto, puedes ahorrarte algunas líneas y quitar de forma segura estas molestas declaraciones de excepción:
/// Kotlin
fun testSomethingImportant() { … }

Omisión de paréntesis con llamadas lambda

En Kotlin, si deseas asignar un cierre a una variable, se debe declarar su tipo explícitamente:
val sumFunc: (Int, Int) -> Int = { x, y -> x + y }
Sin embargo, si todo se puede ser inferir, esto se puede acortar a solo
{ x, y -> x + y }
Por ejemplo:
val intList = listOf(1, 2, 3, 4, 5, 6)
val sum = intList.fold(0, { x, y -> x + y })
Ten en cuenta que, en Kotlin, si el último parámetro de una función es una llamada lambda, puedes escribir el cierre fuera de los paréntesis. Lo anterior es idéntico a
val sum = intList.fold(0) { x, y -> x + y }
Sin embargo, solo porque puedas, no significa que debas hacerlo. Algunas personas dirán que la llamada de arriba se ve rara. En otras ocasiones, esta sintaxis puede generar menos molestia visual, especialmente si el único parámetro del método era un cierre. Supongamos que queremos contar números pares. Compara lo siguiente:
intList.filter({ x -> x % 2 == 0 }).count()
con
intList.filter { x -> x % 2 == 0 }.count()
O bien:
Thread({ doThreadWork() })
con
Thread { doThreadWork() }
Independientemente de cuál creas que es el mejor enfoque, verás esta sintaxis en el código Kotlin, y puede generarse automáticamente mediante la acción Java → Kotlin, así que deberías asegurarte de entender lo que está pasando cuando lo veas.

Igualdades, "==" y "==="

Kotlin se desvía de Java respecto de las pruebas de igualdad.
En Java, por ejemplo, se utiliza la comparación de iguales dobles (==), que es distinta del método de igualdad. Aunque esto puede sonar bien desde un punto de vista teórico, en la práctica, es fácil para un desarrollador utilizar accidentalmente "==" cuando se trata de usar igualdades. Esto podría generar errores sutiles y tomar horas para detectar o depurar.
En Kotlin, "==" es básicamente lo mismo que iguales: la única diferencia es que también procesa correctamente el caso de "null". Por ejemplo, "null == x" es correcto, mientras que "null.equals(x)" lanza un NPE.
Si necesitas comparar instancias en Kotlin, puedes utilizar, en cambio, tres iguales (===). Esta sintaxis es más difícil de usar indebidamente y es más fácil de detectar.
/// Java
Color first = new Color(255, 0, 255);
Color second = new Color(255, 0, 255);
assertThat(first.equals(second)).isTrue();
assertThat(first == second).isFalse();
/// Kotlin
val first = Color(255, 0, 255)
val second = Color(255, 0, 255)
assertThat(first.equals(second)).isTrue()
assertThat(first == second).isTrue()
assertThat(first === second).isFalse()
La mayoría de las veces que escribas código Kotlin, usarás "==", ya que la necesidad de utilizar "===" es bastante rara. Como precaución, el convertidor de Java a Kotlin siempre convertirá "==" a "===". Para mayor legibilidad e intención, considera cambiar a "==" cuando sea posible. Por ejemplo, esto es común en las comparaciones de enumeraciones.
/// Java
if (day == DayOfWeek.MONDAY) { … }
/// Kotlin (auto-converted from Java)
❌ if (day === DayOfWeek.MONDAY) { … }
/// Kotlin
if (day == DayOfWeek.MONDAY) { … }

Eliminación de prefijos de campo

En Java, es común tener un campo privado sincronizado con un captador y receptor público, y muchas bases de código adjuntan un prefijo en el campo, lo que es un vestigio de notación húngara.
/// Java
private String myName;
// or private String mName;
// or private String _name;
public String getName() { … }
public void setName(String name) { … }
Este prefijo pretende ser un marcador útil, solo visible para la implementación de la clase, que facilita la distinción entre los campos locales de la clase y, por ejemplo, los parámetros que se pasan a una función.
En Kotlin, los campos y los captadores y receptores se fusionan en un solo concepto.
/// Kotlin
class User {
  val id: String   // represents field and getter
  var name: String // represents field, getter, and setter
}
Sin embargo, cuando se convierte automáticamente el código, el prefijo Java a veces es arrastrado, y lo que antes era un detalle oculto dentro de la clase puede filtrarse a su interfaz pública.
/// Kotlin (auto-converted from Java)
class User {
❌ val myId: String
❌ var myName: String
}
Para evitar que se filtren los prefijos, acostúmbrate a quitarlos por motivos de coherencia.
Los campos sin prefijos pueden hacer que la revisión ocasional de código mediante herramientas web sea un poco más difícil de leer (por ejemplo, en una función demasiado larga incluida en una clase demasiado grande). Sin embargo, cuando se lee el código dentro de un IDE, queda claro qué valores son campos y cuáles son parámetros debido a su resaltado sintáctico. Quitar prefijos también puede fomentar mejores hábitos de codificación respecto de la escritura de métodos y clases más enfocados.

Consideraciones finales

Esperamos que esta guía te ayude a iniciar tu aprendizaje de Kotlin. Primero, empezarás escribiendo Java y convirtiéndolo a Kotlin, luego escribirás Kotlin como Java, y finalmente, en poco tiempo, ¡estarás escribiendo código Kotlin idiomático como un profesional!
Esta publicación solo muestra una pequeña parte de lo que se puede hacer con Kotlin. Es solo una breve introducción para aquellos que no tienen mucho tiempo y solo necesitan hacer su primera prueba de Kotlin rápidamente, a la vez que presenta una buena parte de los aspectos fundamentales del lenguaje.
Sin embargo, es probable que no abarque todo lo que necesites. En ese caso, consulta la documentación oficial:
Referencia del lenguaje: https://kotlinlang.org/docs/reference/
Instructivos interactivos:
https://try.kotlinlang.org/
La referencia del lenguaje es muy útil. Abarca todos los temas de Kotlin sin llegar a ser tan profunda como para resultar abrumadora.
Los instructivos te darán la oportunidad de practicar el uso del lenguaje, y también incluyen koanes (una serie de ejercicios cortos) para ayudar a confirmar tus conocimientos recién adquiridos.
Y, por último, consulta nuestro codelab oficial sobre cómo refactorizar código en Kotlin. Trata temas presentados en esta entrada de blog y mucho, mucho más.