Este documento es un conjunto de reglas para la creación de APIs públicas en Java y Kotlin con la intención de que el código se sienta idiomático cuando se consuma del otro idioma.
Última actualización: 29 de julio de 2024
Java (para consumo de Kotlin)
No uses palabras clave fijas
No uses ninguna de las palabras clave fijas de Kotlin como nombre de los métodos. o campos. Estas requieren el uso de acentos graves como escape cuando se llame desde Kotlin Palabras clave no definitivas, palabras clave modificadoras y se permiten 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 en Any
para
o los nombres de las propiedades de extensión en Any
para
a menos que sea absolutamente necesario. Si bien los métodos y campos de miembros siempre
tienen prioridad sobre las funciones o propiedades de extensión de Any
, puede
difícil cuando se lee el código para saber a cuál se llama.
Anotaciones de nulabilidad
Todos los tipos de campo, retornos y parámetros no primitivos en una API pública deben tienen una anotación de nulabilidad. Los tipos no anotados se interpretan como “plataforma” tipos, que tienen nulabilidad ambigua.
De forma predeterminada, las marcas del compilador de Kotlin respetan las anotaciones de JSR 305, pero las marcan. con advertencias. También puedes configurar una marca para que el compilador trate las anotaciones como errores.
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 Flowable.create()
de RxJava 2 se define de la siguiente manera:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
Como FlowableOnSuscríbete es apto para la conversión de SAM, las llamadas a función de este método de Kotlin se verá de la siguiente manera:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Sin embargo, si los parámetros se revirtieron en la firma del método, las llamadas a funciones podrías usar la sintaxis end-lambda:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefijos de propiedad
Para que un método se represente como una propiedad en Kotlin, usa el estilo estricto "bean" y se debe usar un prefijo.
Los métodos de acceso requieren un prefijo get
o, para los métodos que muestran valores booleanos, un is
.
public final class User {
public String getName() { /* … */ }
public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()
Los métodos de mutador asociados requieren un prefijo set
.
public final class User {
public String getName() { /* … */ }
public void setName(String name) { /* … */ }
public boolean isActive() { /* … */ }
public void setActive(boolean active) { /* … */ }
}
user.name = "Bob" // Invokes user.setName(String)
user.isActive = true // Invokes user.setActive(boolean)
Si quieres que los métodos se expongan como propiedades, no uses prefijos no estándares, como
has
, set
o descriptores de acceso sin el prefijo get
. Métodos con prefijos no estándar
aún se pueden llamar como funciones, las cuales pueden ser aceptables según el
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 (como sobrecarga del operador en Kotlin). Asegúrate de que los nombres de los métodos tiene sentido usarlos 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 resulta atractivo y filtra el lenguaje como una implementación
en detalle.
Te recomendamos agregar @file:JvmMultifileClass
para combinar los miembros de nivel superior de
varios archivos en una sola clase.
Argumentos lambda
Las interfaces de método único (SAM) definidas en Java se pueden implementar en Kotlin y Java con la sintaxis lambda, que intercala la implementación en un lenguaje de una nueva manera. Kotlin tiene varias opciones para definir esas interfaces, cada una con una leve la diferencia.
Definición preferida
Funciones de orden superior diseñadas para usarse desde Java
no debería tomar los tipos de funciones que devuelvan Unit
como lo harían
Requerir que los llamadores de Java devuelvan Unit.INSTANCE
. En lugar de intercalar la función
escribe la firma, usa interfaces funcionales (SAM). También
considera usar interfaces funcionales (SAM) en lugar de
cuando se definen interfaces que se espera que se usen como lambdas,
que permite el uso idiomático de Kotlin.
Considera esta definición de Kotlin:
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
Cuando se invoca desde Kotlin:
sayHi { println("Hello, $it!") }
Cuando se invoca desde Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Incluso cuando el tipo de función no devuelve un Unit
, podría ser una buena idea convertirla en una interfaz con nombre para permitir que los llamadores la implementen con una clase con nombre y no solo con lambdas (en ambos casos, y Java).
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
Evita los tipos de funciones que devuelven Unit
Considera esta definición de Kotlin:
fun sayHi(greeter: (String) -> Unit) = /* … */
Requiere que los llamadores de Java devuelvan Unit.INSTANCE
:
sayHi(name -> {
System.out.println("Hello, " + name + "!");
return Unit.INSTANCE;
});
Evite las interfaces funcionales cuando la implementación deba tener estado
Cuando la implementación de la interfaz debe tener un estado, el uso de la sintaxis lambda no tiene sentido. Comparable es un ejemplo destacado,
ya que está diseñado para comparar this
con other
, y las lambdas no tienen this
. No
Si se agrega el prefijo fun
a la interfaz, se fuerza al llamador a usar object : ...
.
de texto, que le permite tener un estado y le proporciona una sugerencia al llamador.
Considera esta definición de Kotlin:
// No "fun" prefix.
interface Counter {
fun increment()
}
Evita la sintaxis lambda en Kotlin, que requiere esta versión más larga:
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
Evita usar parámetros Nothing
genéricos
Un tipo cuyo parámetro genérico es Nothing
se expone como tipos sin procesar en Java. Sin procesar
tipos de bloques se usan rara vez en Java y deberían evitarse.
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 devuelven colecciones de solo lectura compartidas o sin propietario de APIs públicas, unir en un contenedor que no se puede modificar o hacer una copia defensiva. A pesar de Kotlin aplicación de su propiedad de solo lectura, no existe tal aplicación en Java lado derecho. Sin el wrapper o la copia defensiva, los invariantes pueden ser violados por mostrar una referencia de colección de larga duración.
Funciones complementarias
Las funciones públicas de un objeto complementario deben tener anotaciones con @JvmStatic
se expongan como un método estático.
Sin la anotación, estas funciones solo están disponibles como métodos de instancia
en un campo Companion
estático.
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
Las propiedades públicas no const
que son constantes efectivas de un companion
object
deben anotarse con @JvmField
para exponerlas como un campo estático.
Sin la anotación, estas propiedades solo están disponibles como nombres extraños
"método get" de la instancia en el campo estático Companion
. Se está usando @JvmStatic
en su lugar
de @JvmField
mueve a los "getters" con nombres extraños. a métodos estáticos en la clase,
que aún es incorrecta.
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
para diseñar nombres que se sientan idiomáticos
para las convenciones de ambos lenguajes o para que coincida con su respectiva biblioteca estándar
de nombres.
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<T : Any>
data class Some<T : Any>(val value: T): Optional<T>()
object None : Optional<Nothing>()
@JvmName("ofNullable")
fun <T> 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<String> 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
tienen sentido. De lo contrario, realiza una o ambas de las siguientes refactorizaciones
hasta que esté 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 comprobaciones de Android Lint que te ayudarán a detectar y marcar algunos de los problemas de interoperabilidad descritos anteriormente. Solo problemas en Java (para Kotlin consumo). 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 Archivo > Preferencias > Editor > Inspecciones y Verifica las reglas que deseas habilitar en la Interoperabilidad de Kotlin:
Una vez que hayas marcado las reglas que deseas habilitar, las nuevas verificaciones se cuando ejecutas 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
:
Groovy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
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.