Este documento é um conjunto de regras para a criação de APIs públicas em Java e Kotlin com a intenção de que o código pareça idiomático quando consumido por outro idioma de destino.
Última atualização: 29/07/2024
Java (para consumo de Kotlin)
Nenhuma palavra-chave específica
Não use nenhuma das palavras-chave específicas do Kotlin como nome dos métodos. ou campos. Eles exigem o uso de acentos graves para escapar ao chamar de Kotlin Palavras-chave não relacionadas, palavras-chave modificadoras e identificadores especiais são permitidos.
Por exemplo, a função when
do Mockito requer crases 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
ou os nomes das propriedades de extensão em Any
para
campos, a menos que seja absolutamente necessário. Embora os métodos e campos dos membros sempre
têm precedência sobre as funções ou propriedades de extensão de Any
, podem ser
difícil ao ler o código para saber qual está sendo chamado.
Anotações de nulidade
Cada parâmetro, retorno e tipo de campo não primitivo em uma API pública precisa têm uma anotação de nulidade. Os tipos não anotados são interpretados como "plataforma" tipos, que têm nulidade ambígua.
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 a conversão de SAM precisam ser os últimos (link em inglês).
Por exemplo, a assinatura do método Flowable.create()
do RxJava 2 é definida como:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
Como o FlowableOnSubscription está qualificado para a conversão de SAM, as chamadas de função de o método do Kotlin vai ficar assim:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
No entanto, se os parâmetros forem invertidos na assinatura do método, as chamadas de função poderia usar a sintaxe de lambda final:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefixos de propriedade
Para que um método seja representado como uma propriedade no Kotlin, o estilo "bean" é estrito de codificador-decodificador precisa ser usado.
Os métodos do acessador exigem um prefixo get
ou, para métodos que retornam booleanos, um 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()
Os métodos de mutação 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 expostos como propriedades, não use prefixos não padrão como
Acessores has
, set
ou sem o prefixo get
. Métodos com prefixos não padrão
ainda podem ser chamadas como funções, o que pode ser aceitável dependendo da
do método.
Sobrecarga do operador
Atente-se aos nomes de métodos que permitem sintaxe especial do local da chamada (como sobrecarga de operadores no Kotlin). Certifique-se de que os nomes dos métodos então faz sentido usar 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 tiver funções ou propriedades de nível superior, sempre anote-o.
com @file:JvmName("Foo")
para fornecer um bom nome.
Por padrão, os membros de nível superior em um arquivo MyClass.kt acabarão em uma classe chamada
MyClassKt
, que não é atraente e vaza a linguagem como uma implementação.
detalhes.
Adicione @file:JvmMultifileClass
para combinar os membros de nível superior
diversos arquivos em uma única classe.
Argumentos lambda
As interfaces de método único (SAM, na sigla em inglês) definidas em Java podem ser implementadas em Kotlin e Java usando a sintaxe lambda, que alinha a implementação em uma linguagem idiomática de um jeito fácil. O Kotlin tem várias opções para definir essas interfaces, cada uma com uma pequena diferença.
Definição preferencial
Funções de ordem superior que precisam ser usadas em Java
não use tipos de função que retornam Unit
porque isso
exigem que os autores das chamadas Java retornem Unit.INSTANCE
. Em vez de inserir a função em linha
digite a assinatura, use interfaces funcionais (SAM). Além disso,
considere usar interfaces funcionais (SAM) em vez de comuns.
ao definir aquelas que precisam ser usadas como lambdas,
que permite o uso idiomático do Kotlin.
Confira esta definição do Kotlin:
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
Quando invocada usando Kotlin:
sayHi { println("Hello, $it!") }
Quando invocada usando Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Mesmo quando o tipo de função não retorna uma Unit
, ainda pode ser uma boa
ideia transformá-la em uma interface nomeada. Isso permite que os autores da chamada a implementem com uma classe
nomeada, e não apenas com lambdas (tanto em Kotlin como em Java).
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
Evitar tipos de função que retornam Unit
Confira esta definição do Kotlin:
fun sayHi(greeter: (String) -> Unit) = /* … */
Ela exige que os autores das chamadas Java retornem Unit.INSTANCE
:
sayHi(name -> {
System.out.println("Hello, " + name + "!");
return Unit.INSTANCE;
});
Evitar interfaces funcionais quando a implementação precisa ter o estado
Quando a implementação da interface precisa ter um estado, o uso da sintaxe
lambda não faz sentido. Comparable é um exemplo notório,
já que ele foi feito para comparar this
com other
, e lambdas não têm this
. Não
prefixar a interface com fun
força o autor da chamada a usar object : ...
.
, o que permite que ele tenha um estado, fornecendo uma dica ao autor da chamada.
Confira esta definição do Kotlin:
// No "fun" prefix.
interface Counter {
fun increment()
}
Ela impede a sintaxe lambda no Kotlin, exigindo esta versão mais longa:
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
Evitar Nothing
genérico
Um tipo com parâmetro genérico Nothing
é exposto como tipos brutos para Java. Bruto
são raramente usados em Java e devem 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 somente leitura compartilhadas ou sem proprietário de APIs públicas, junte em um contêiner não modificável ou fazer uma cópia defensiva. Apesar do Kotlin aplicando sua propriedade somente leitura, não existe tal aplicação no lado. Sem o wrapper ou a cópia defensiva, as invariantes podem ser violadas por retornando uma referência de coleção de longa duração.
Funções complementares
As funções públicas em um objeto complementar precisam ser anotadas com @JvmStatic
.
a ser exposto como um método estático.
Sem a anotação, essas funções estarão disponíveis apenas como métodos de instância
em um campo Companion
estático.
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 só ficam disponíveis como nomes estranhos
instância "getters" no campo Companion
estático. Usando @JvmStatic
de @JvmField
move os "getters" com um nome estranho a 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 que pareçam idiomáticos
para as convenções de ambas as linguagens ou para corresponder às respectivas bibliotecas padrão
e nomeação.
Isso ocorre com mais frequência para funções e propriedades de extensão porque a localização do tipo de receptor é 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 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 cada um deles
que façam sentido. Caso contrário, execute uma das refatorações a seguir ou ambas
até ficar satisfeito:
- Mude a ordem dos parâmetros para dar preferência aos que têm o padrão para o fim.
- 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 que ajudarão você a detectar e sinalizar alguns os problemas de interoperabilidade descritos anteriormente. Somente problemas em Java (para Kotlin consumo de energia) são detectados. 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, vá para Arquivo > Preferências > Editor > inspeções e Marque as regras que você quer ativar em "Interoperabilidade do Kotlin":
Depois de marcar as regras que você quer ativar, as novas verificações executar ao executar as inspeções de código (Analyze > Inspect Code...)
Builds de linha de comando
Para ativar essas verificações nos builds de linha de comando, adicione a seguinte linha em
seu 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.