Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Guía de interoperabilidad de Kotlin-Java

Este documento es un conjunto de reglas para crear API públicas en Java y Kotlin con la intención de que el código se sienta idiomático cuando se consume desde el otro lenguaje.

Última actualización: 18 de mayo de 2018

Java (para consumo de Kotlin)

No uses palabras clave fijas

No utilices ninguna de las palabras clave fijas de Kotlin como nombre de métodos o campos, ya que requieren el uso de acentos graves como forma escapada cuando se las llama desde Kotlin. Se permiten palabras clave no fijas, palabras clave modificadoras e identificadores especiales.

Por ejemplo, la función when de Mockito requiere un acento grave cuando se usa desde Kotlin:

val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)

Evita usar nombres de extensión Any

Evita usar los nombres de las funciones de extensión de Any para los métodos o los nombres de las propiedades de extensión de Any para los campos, a menos que sea absolutamente necesario. Si bien los métodos y campos de miembros siempre tendrán prioridad sobre las funciones o propiedades de extensión de Any, puede ser difícil saber a cuál se llama cuando se lee el código.

Anotaciones de nulabilidad

Cada uno de los tipos de campo, valores que se muestran y parámetros no primitivos de una API pública debe tener una anotación de nulabilidad. Los tipos no anotados se interpretan como tipos de "plataforma", que tienen nulabilidad ambigua.

Las anotaciones del paquete JSR 305 podrían usarse para configurar un valor predeterminado razonable, pero actualmente no se recomienda. Estas requieren que el compilador respete una marca de aceptación, lo que entra en conflicto con el sistema de módulos de Java 9.

Los parámetros lambda van al final

Los tipos de parámetros aptos para la conversión de SAM deben ser los últimos.

Por ejemplo, la firma del método RxJava 2’s Flowable.create() se define como:

public static  Flowable create(
    FlowableOnSubscribe source,
    BackpressureStrategy mode) { /* … */ }

Debido a que FlowableOnSubscribe es apto para la conversión de SAM, las llamadas de función de este método desde Kotlin se ven así:

Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)

Sin embargo, si los parámetros se revirtieron en la firma del método, las llamadas a funciones podrían usar la sintaxis trailing-lambda:

Flowable.create(BackpressureStrategy.LATEST) { /* … */ }

Prefijos de propiedad

Para que se represente un método como una propiedad en Kotlin, se debe usar el prefijo estricto de estilo "bean".

Los métodos de descriptores de acceso requieren un prefijo "get", mientras que los métodos que muestran valores booleanos pueden usar el prefijo "is".

public final class User {
  public String getName() { /* … */ }
  public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.active // Invokes user.isActive()

Los métodos de mutador asociados requieren el prefijo "set".

public final class User {
  public String getName() { /* … */ }
  public void setName(String name) { /* … */ }
}
user.name = "Bob" // Invokes user.setName(String)

Si deseas que se expongan los métodos como propiedades, no uses prefijos no estándares como "has" o "set" ni descriptores de acceso sin prefijo "get". Los métodos con prefijos no estándares aún se pueden llamar como funciones que pueden ser aceptables según el comportamiento del método.

Sobrecarga del operador

Ten en cuenta los nombres de métodos que permiten una sintaxis especial del sitio de llamada (es decir, la sobrecarga del operador) en Kotlin. Asegúrate de que tenga sentido usar los nombres de los métodos con la sintaxis abreviada.

public final class IntBox {
  private final int value;
  public IntBox(int value) {
    this.value = value;
  }
  public IntBox plus(IntBox other) {
    return new IntBox(value + other.value);
  }
}
val one = IntBox(1)
val two = IntBox(2)
val three = one + two // Invokes one.plus(two)

Kotlin (para consumo de Java)

Nombre del archivo

Cuando un archivo contenga funciones o propiedades de nivel superior, siempre anótalo con @file:JvmName("Foo") para proporcionar un buen nombre.

De forma predeterminada, los miembros de nivel superior de un archivo MyClass.kt terminarán en una clase llamada MyClassKt, que no es atractiva y que filtra el lenguaje como un detalle de implementación.

Procura agregar @file:JvmMultifileClass para combinar los miembros de nivel superior de múltiples archivos en una única clase.

Argumentos lambda

Los tipos de función que están destinados a ser utilizados desde Java deben evitar el tipo de datos Unit que se muestra. Para ello, debes especificar una declaración return Unit.INSTANCE; explícita que no sea idiomática.

fun sayHi(callback: (String) -> Unit) = /* … */
// Kotlin caller:
greeter.sayHi { Log.d("Greeting", "Hello, $it!") }
// Java caller:
greeter.sayHi(name -> {
    Log.d("Greeting", "Hello, " + name + "!");
    return Unit.INSTANCE;
});

Esta sintaxis tampoco permite proporcionar un tipo con nombre semántico de modo que pueda implementarse en otros tipos.

Definir una interfaz con nombre y método abstracto único (SAM) en Kotlin para el tipo lambda corrige el problema para Java, pero evita que se use la sintaxis lambda en Kotlin.

interface GreeterCallback {
    fun greetName(name: String): Unit
}

fun sayHi(callback: GreeterCallback) = /* … */
// Kotlin caller:
greeter.sayHi(object : GreeterCallback {
    override fun greetName(name: String) {
        Log.d("Greeting", "Hello, $name!")
    }
})
// Java caller:
greeter.sayHi(name -> Log.d("Greeting", "Hello, " + name + "!"))

La definición de una interfaz SAM con nombre en Java permite el uso de una versión ligeramente inferior de la sintaxis lambda de Kotlin en la que el tipo de interfaz debe especificarse de manera explícita.

// Defined in Java:
interface GreeterCallback {
    void greetName(String name);
}
fun sayHi(greeter: GreeterCallback) = /* … */
// Kotlin caller:
greeter.sayHi(GreeterCallback { Log.d("Greeting", "Hello, $it!") })
// Java caller:
greeter.sayHi(name -> Log.d("Greeter", "Hello, " + name + "!"));

En la actualidad, no hay forma de definir un tipo de parámetro para usar como lambda desde Java o Kotlin que se sienta idiomático en ambos lenguajes. La recomendación actual es priorizar el tipo de función, a pesar de la experiencia degradada de Java cuando el tipo de datos es Unit.

Evita usar parámetros Nothing genéricos

Un tipo cuyo parámetro genérico es Nothing se expone como tipos sin formato en Java. Los tipos sin formato se usan con poca frecuencia en Java y se deben evitar.

Excepciones de documentos

Las funciones que pueden arrojar excepciones verificadas deben documentarse con @Throws. Las excepciones de tiempo de ejecución deben estar documentadas en KDoc.

Ten en cuenta la función que se delega a las API, ya que pueden arrojar excepciones verificadas que Kotlin, de otra forma, permite propagar de forma silenciosa.

Copias defensivas

Cuando se muestren colecciones de solo lectura compartidas o sin propietario de las API públicas, únelas en un contenedor que no se pueda modificar o realiza una copia defensiva. Kotlin aplica su propiedad de solo lectura, pero esa opción no está disponible en Java. Sin el wrapper o la copia defensiva, es posible vulnerar objetos invariantes mostrando una referencia de colección permanente.

Funciones complementarias

Para exponer como un método estático las funciones públicas de un objeto complementario, hay que anotarlas con @JvmStatic.

Sin la anotación, esas funciones solo están disponibles como métodos de instancia en un campo estático Companion.

Incorrecto: Sin anotación

class KotlinClass {
    companion object {
        fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.Companion.doWork();
    }
}

Correcto: Anotación @JvmStatic

class KotlinClass {
    companion object {
        @JvmStatic fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.doWork();
    }
}

Constantes complementarias

Para exponer como un campo estático las propiedades públicas no const que son constantes efectivas de un companion object, hay que anotarlas con @JvmField.

Sin la anotación, estas propiedades solo están disponibles como "getters" de instancias con nombres extraños en el campo Companion estático. Si usas @JvmStatic en lugar de @JvmField, los "getters" con nombres extraños se convertirán en métodos estáticos de la clase, lo que sigue siendo incorrecto.

Incorrecto: Sin anotación

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.Companion.getBIG_INTEGER_ONE());
    }
}

Incorrecto: Anotación @JvmStatic

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        @JvmStatic val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.getBIG_INTEGER_ONE());
    }
}

Correcto: Anotación @JvmField

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.BIG_INTEGER_ONE);
    }
}

Nomenclatura idiomática

Kotlin tiene diferentes convenciones de llamadas respecto de Java que pueden cambiar la forma en que nombras funciones. Usa @JvmName a fin de diseñar nombres que se sientan idiomáticos para las convenciones de ambos lenguajes o para que coincidan con las nomenclaturas de las bibliotecas estándares correspondientes.

Esto ocurre con mayor frecuencia para las funciones y propiedades de extensión porque la ubicación del tipo de receptor es diferente.

sealed class Optional
data class Some(val value: T): Optional()
object None : Optional()

@JvmName("ofNullable")
fun  T?.asOptional() = if (this == null) None else Some(this)
// FROM KOTLIN:
fun main(vararg args: String) {
    val nullableString: String? = "foo"
    val optionalString = nullableString.asOptional()
}
// FROM JAVA:
public static void main(String... args) {
    String nullableString = "Foo";
    Optional optionalString =
          Optionals.ofNullable(nullableString);
}

Sobrecargas de funciones predeterminadas

Las funciones con parámetros que tienen un valor predeterminado deben usar @JvmOverloads. Sin esta anotación, es imposible invocar la función con cualquier valor predeterminado.

Cuando uses @JvmOverloads, inspecciona los métodos generados para asegurarte de que cada uno tenga sentido. De lo contrario, realiza una de las siguientes refactorizaciones, o ambas, hasta que estés satisfecho:

  • Cambia el orden de los parámetros para que los que tengan valores predeterminados se dirijan hacia el final.
  • Mueve los valores predeterminados a sobrecargas de funciones manuales.

Incorrecto: Sin @JvmOverloads

class Greeting {
    fun sayHello(prefix: String = "Mr.", name: String) {
        println("Hello, $prefix $name")
    }
}
public class JavaClass {
    public static void main(String... args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("Mr.", "Bob");
    }
}

Correcto: Anotación @JvmOverloads

class Greeting {
    @JvmOverloads
    fun sayHello(prefix: String = "Mr.", name: String) {
        println("Hello, $prefix $name")
    }
}
public class JavaClass {
    public static void main(String... args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("Bob");
    }
}

Verificaciones de Lint

Requisitos

  • Versión de Android Studio: 3.2 Canary 10 o posteriores
  • Versión del complemento de Gradle para Android: 3.2 o posteriores

Verificaciones compatibles

Ahora hay verificaciones de Android Lint que te ayudarán a detectar y marcar algunos de los problemas de interoperabilidad descritos con anterioridad. En este momento, solo se detectan problemas en Java (para consumo de Kotlin). Específicamente, las verificaciones compatibles son las siguientes:

  • Nulabilidad desconocida
  • Acceso a la propiedad
  • No usar palabras clave fijas de Kotlin
  • Los parámetros lambda van al final

Android Studio

Para habilitar estas verificaciones, ve a File > Preferences > Editor > Inspections y verifica las reglas que deseas habilitar en Kotlin Interoperability:

Figura 1: Configuración de interoperabilidad de Kotlin en Android Studio

Una vez que hayas verificado las reglas que deseas habilitar, se ejecutarán las nuevas verificaciones cuando ejecutes las inspecciones de tu código (Analyze > Inspect Code…)

Compilaciones de líneas de comandos

Para habilitar estas verificaciones desde las compilaciones de líneas de comandos, agrega la siguiente línea en tu archivo build.gradle:

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Para conocer el conjunto completo de configuraciones admitidas dentro de lintOptions, consulta la referencia Gradle DSL de Android.

A continuación, ejecuta ./gradlew lint desde la línea de comandos.