O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Guia de interoperabilidade entre Kotlin e Java

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).

As anotações do pacote JSR 305 podem ser usadas para configurar um padrão razoável, mas não são recomendadas atualmente. Elas exigem uma sinalização de permissão a ser honrada pelo compilador e entram em conflito com o sistema de módulos do Java 9.

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 static  Flowable 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.active // Invokes user.isActive()

Os métodos mutator associados exigem um prefixo "set".

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

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) 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 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 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 nas compilações de linha de comando, adicione a seguinte linha no arquivo build.gradle:

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Para ver o conjunto completo de configurações compatíveis com lintOptions, consulte a referência Gradle DSL para Android (link em inglês).

Em seguida, execute ./gradlew lint na linha de comando.