Este documento é composto por um conjunto de regras para a criação de APIs públicas em Java e Kotlin para que o código se torne idiomático quando consumido a partir de outra linguagem.
Última atualização: 18/05/2018
Java (para consumo de Kotlin)
Nenhuma palavra-chave específica
Não use nenhuma das palavras-chave específicas do Kotlin, como nome de métodos ou campos. Eles requerem o uso de crase para escape quando chamados no Kotlin. Palavras-chave não específicas, palavras-chave modificadoras e identificadores especiais são permitidos (links em inglês).
Por exemplo, a função when
do Mockito requer crase quando usada no Kotlin:
val callable = Mockito.mock(Callable::class.java) Mockito.`when`(callable.call()).thenReturn(/* … */)
Evitar nomes de extensão Any
Evite usar os nomes das funções de extensão em Any
para métodos ou os nomes das propriedades de extensão em Any
para campos, a menos que seja absolutamente necessário (links em inglês). Embora os métodos e campos dos membros sempre tenham precedência sobre as funções ou propriedades de extensão de Any
, pode ser difícil saber qual deles está sendo chamado ao ler o código.
Anotações de nulidade
Cada parâmetro, retorno e tipo de campo não primitivo em uma API pública precisa ter uma anotação de nulidade. Os tipos não anotados são interpretados como tipos "plataforma", que têm nulidade ambígua (link em inglês).
Por padrão, as sinalizações do compilador Kotlin respeitam as anotações JSR 305, mas as sinalizam com avisos. Também é possível definir uma sinalização para que o compilador trate as anotações como erros.
Parâmetros lambda por último
Os tipos de parâmetro qualificados para conversão de SAM precisam ser os últimos (link em inglês).
Por exemplo, a assinatura do método RxJava 2’s Flowable.create()
é definida como:
public staticFlowable create( FlowableOnSubscribe source, BackpressureStrategy mode) { /* … */ }
Como FlowableOnSubscribe está qualificado para a conversão de SAM, as chamadas de função desse método no Kotlin têm esta aparência:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
No entanto, se os parâmetros fossem invertidos na assinatura do método, as chamadas de função poderiam usar a sintaxe de lambda no final:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefixos de propriedade
Para que um método seja representado como uma propriedade no Kotlin, o prefixo restrito de tipo “bean” precisa ser usado.
Os métodos de acesso exigem um prefixo "get" ou, para métodos de retorno de booleano, um prefixo "is" pode ser usado.
public final class User { public String getName() { /* … */ } public boolean isActive() { /* … */ } }
val name = user.name // Invokes user.getName() val active = user.isActive // Invokes user.isActive()
Os métodos mutator associados exigem um prefixo "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)
Se você quiser que os métodos sejam exibidos como propriedades, não use prefixos diferentes do padrão, como os métodos acessor prefixados ‘has’/’set’ ou non-‘get’. Métodos com prefixos fora do padrão ainda podem ser chamados como funções que podem ser aceitáveis dependendo do comportamento do método.
Sobrecarga do operador
Tenha cuidado com os nomes de método que permitem sintaxe especial do local da chamada, ou seja, sobrecarga do operador (link em inglês) no Kotlin. Os nomes de métodos como esses precisam ser usados com a sintaxe 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)
Nome do arquivo
Quando um arquivo contém funções ou propriedades de nível superior, sempre anote com @file:JvmName("Foo")
para fornecer um bom nome.
Por padrão, os membros de nível superior em um arquivo MyClass.kt acabam em uma classe chamada MyClassKt
, o que não é atraente e vaza a linguagem como um detalhe de implementação.
Considere adicionar @file:JvmMultifileClass
para combinar os membros de nível superior de vários arquivos em uma única classe.
Argumentos lambda
Os tipos de função
específicos para serem usados no Java precisam evitar o tipo de retorno
Unit
(link em inglês). Para fazer isso, é necessário especificar uma instrução return
Unit.INSTANCE;
explícita que seja não 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; });
Essa sintaxe também não permite o fornecimento de um tipo nomeado semanticamente, de modo que possa ser implementado em outros tipos.
Definir uma interface nomeada, de método abstrato único (SAM) no Kotlin para o tipo lambda, corrige o problema para Java, mas impede que a sintaxe lambda seja usada no 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 + "!"))
Definir uma interface SAM nomeada em Java permite o uso de uma versão um pouco inferior da sintaxe lambda do Kotlin, em que o tipo de interface precisa ser explicitamente especificado.
// 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 + "!"));
No momento, não é possível definir um tipo de parâmetro para ser usado como um lambda de Java e Kotlin, de modo que pareça idiomático de ambas as linguagens. A recomendação atual é a preferência pelo tipo de função, apesar da experiência degradada do Java quando o tipo de retorno é Unit
.
Evitar Nothing
genérico
Um tipo cujo parâmetro genérico é Nothing
é exposto como tipos brutos para Java. Os tipos brutos raramente são usados em Java e precisam ser evitados.
Exceções de documentos
As funções que podem lançar exceções verificadas precisam documentá-las com @Throws
. Exceções de tempo de execução precisam ser documentadas no KDoc.
Tenha cuidado com as APIs para as quais uma função delega porque elas podem gerar exceções verificadas que, de outra forma, o Kotlin permite propagar silenciosamente.
Cópias defensivas
Ao retornar coleções de somente leitura sem dono ou compartilhadas em APIs public, una-as em um contêiner inalterável ou faça uma cópia defensiva. Apesar de o Kotlin impor as próprias propriedades de somente leitura, não existe tal aplicação no lado do Java. Sem o wrapper ou a cópia defensiva, as invariantes podem ser violadas retornando uma referência de coleção de vida longa.
Funções complementares
As funções públicas em um objeto complementar precisam ser anotadas com @JvmStatic
para serem expostas como um método estático.
Sem a anotação, essas funções só estarão disponíveis como métodos de instância em um campo estático Companion
.
Incorreto: sem anotações
class KotlinClass { companion object { fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.Companion.doWork(); } }
Correto: anotação @JvmStatic
class KotlinClass { companion object { @JvmStatic fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.doWork(); } }
Constantes complementares
Propriedades públicas, não const
, que são constantes efetivas em um companion object
precisam ser anotadas com @JvmField
para serem expostas como um campo estático.
Sem a anotação, essas propriedades estarão disponíveis apenas como instâncias de nome estranho "getters" no campo Companion
estático. O uso de @JvmStatic
em vez de @JvmField
move os "getters" com um nome estranho para métodos estáticos na classe, o que ainda está incorreto.
Incorreto: sem anotações
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()); } }
Incorreto: anotação @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()); } }
Correto: anotação @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); } }
Nomeação idiomática
O Kotlin tem convenções de chamada diferentes do Java, e elas podem mudar a forma como você nomeia as funções. Use @JvmName
para criar nomes de modo que eles pareçam idiomáticos para as convenções de ambas as linguagens ou para que correspondam aos nomes das respectivas bibliotecas padrão.
Isso ocorre com mais frequência para funções e propriedades de extensão porque o local do tipo de receptor é diferente.
sealed class Optionaldata 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"; OptionaloptionalString = Optionals.ofNullable(nullableString); }
Sobrecargas de função para padrões
As funções com parâmetros que têm um valor padrão precisam usar @JvmOverloads
.
Sem essa anotação, é impossível invocar a função usando qualquer valor padrão.
Ao usar @JvmOverloads
, inspecione os métodos gerados para garantir que eles façam sentido. Se isso não acontecer, execute uma ou as duas refatorações a seguir até conseguir um resultado satisfatório:
- Mude a ordem dos parâmetros e dê preferências àqueles com padrões voltados para o final.
- Mova os padrões para sobrecargas de funções manuais.
Incorreto: sem@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"); } }
Correto: anotação @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"); } }
Verificações de lint
Requisitos
- Versão do Android Studio: 3.2 Canary 10 ou posterior
- Versão do Plug-in do Android para Gradle: 3.2 ou posterior
Verificações compatíveis
Agora existem verificações do Android Lint para que você possa detectar e sinalizar alguns dos problemas de interoperabilidade descritos acima. Apenas problemas no Java (para consumo de Kotlin) são detectados no momento. Especificamente, as verificações compatíveis são:
- nulidade desconhecida;
- acesso de propriedade;
- sem palavra-chave específica do Kotlin;
- parâmetros lambda por último.
Android Studio
Para ativar essas verificações, acesse File > Preferences > Editor > Inspections e verifique as regras que você quer ativar em "Kotlin Interoperability":
Figura 1. Configurações de interoperabilidade do Kotlin no Android Studio.
Depois de verificar as regras que você quer ativar, as novas verificações serão executadas quando você executar as inspeções do código ( Analyze > Inspect Code…)
Compilações de linha de comando
Para ativar essas verificações em builds de linha de comando, adicione a linha a seguir
no arquivo build.gradle
:
Groovy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Para ver o conjunto completo de configurações com suporte a lintOptions, consulte a referência Gradle DSL (link em inglês) para Android.
Em seguida, execute ./gradlew lint
na linha de comando.