Do lado de fora, pode parecer que o Kotlin tenha simplificado o desenvolvimento do Android sem introduzir novas dificuldades: a linguagem é compatível com Java; portanto, mesmo um grande projeto Java pode ser gradualmente traduzido para ela sem incomodar ninguém, certo? Mas se você olhar mais fundo, em cada caixa há um fundo duplo e na penteadeira há uma porta secreta. Linguagens de programação são projetos muito complexos para serem combinados sem nuances complicadas.
Obviamente, isso não significa "tudo está ruim e você não precisa usar o Kotlin com Java", mas significa que você deve conhecer as nuances e levá-las em consideração. Em nossa conferência
Mobius , Sergei Ryabov
falou sobre como escrever código no Kotlin que seria convenientemente acessado a partir de Java. E o público gostou tanto do relatório que não apenas decidimos postar um vídeo, mas também fizemos uma versão em texto para Habr:
Escrevo o Kotlin há mais de três anos, agora apenas nele, mas, a princípio, arrastei o Kotlin para projetos Java existentes. Portanto, a pergunta "como amarrar Java e Kotlin" no meu caminho surgiu com bastante frequência.
Muitas vezes, quando você adiciona o Kotlin a um projeto, pode ver como isso ...
compile 'rxbinding:xyx' compile 'rxbinding-appcompat-v7:xyx' compile 'rxbinding-design:xyx' compile 'autodispose:xyz' compile 'autodispose-android:xyz' compile 'autodispose-android-archcomponents:xyz'
... se transforma nisso:
compile 'rxbinding:xyx' compile 'rxbinding-kotlin:xyx' compile 'rxbinding-appcompat-v7:xyx' compile 'rxbinding-appcompat-v7-kotlin:xyx' compile 'rxbinding-design:xyx' compile 'rxbinding-design-kotlin:xyx' compile 'autodispose:xyz' compile 'autodispose-kotlin:xyz' compile 'autodispose-android:xyz' compile 'autodispose-android-kotlin:xyz' compile 'autodispose-android-archcomponents:xyz' compile 'autodispose-android-archcomponents-kotlin:xyz'
As especificidades dos últimos dois anos: as bibliotecas mais populares adquirem wrappers para que possam ser usadas no Kotlin de maneira mais lingüística.
Se você escreveu no Kotlin, sabe que existem funções interessantes de extensão, funções embutidas, expressões lambda que estão disponíveis no Java 6. E isso é legal, isso nos atrai para o Kotlin, mas a questão surge. Um dos maiores e mais divulgados recursos da linguagem é a interoperabilidade com Java. Se você levar em conta todos os recursos listados, por que não apenas escrever bibliotecas no Kotlin? Todos eles funcionarão perfeitamente com o Java e você não precisará oferecer suporte a todos esses wrappers, todos ficarão felizes e satisfeitos.
Mas, é claro, na prática, nem tudo é tão cor-de-rosa quanto nas brochuras, sempre há um "atributo de fonte pequena", há arestas vivas na junção de Kotlin e Java, e hoje falaremos um pouco sobre isso.
Bordas afiadas
Vamos começar com as diferenças. Por exemplo, você sabia que no Kotlin não existem palavras-chave voláteis, sincronizadas, strictfp, transitórias? Eles são substituídos por anotações com o mesmo nome, localizadas no pacote kotlin.jvm. Portanto, a maior parte da conversa abordará o conteúdo deste pacote.
Existe a
Timber - uma abstração dessa biblioteca sobre madeireiros do notório
Zheka Vartanov . Ele permite que você o use em qualquer lugar do seu aplicativo e tudo para onde você deseja enviar logs (para o logcat, ou para o servidor para análise, relatório de falhas etc.) se transforma em plug-ins.
Vamos imaginar, por exemplo, que queremos escrever uma biblioteca semelhante, apenas para análise. Desengate também.
object Analytics { fun send(event: Event) {} fun addPlugins(plugs: List<Plugin>) {} fun getPlugins(): List<Plugin> {} } interface Plugin { fun init() fun send(event: Event) fun close() } data class Event( val name: String, val context: Map<String, Any> = emptyMap() )
Adotamos o mesmo padrão de construção, temos um ponto de entrada - este é o Analytics. Podemos enviar eventos para lá, adicionar plugins e ver o que já adicionamos lá.
O plug-in é uma interface de plug-in que abstrai uma API analítica específica.
E, de fato, a classe Event contendo a chave e nossos atributos que enviamos. Aqui, o relatório não diz se vale a pena usar singletones, então não vamos criar um holivar, mas veremos como pentear tudo isso.
Agora um pouco de mergulho. Aqui está um exemplo de como usar nossa biblioteca no Kotlin:
private fun useAnalytics() { Analytics.send(Event("only_name_event")) val props = mapOf( USER_ID to 1235, "my_custom_attr" to true ) Analytics.send(Event("custom_event", props)) val hasPlugins = Analytics.hasPlugins Analytics.addPlugin(EMPTY_PLUGIN)
Em princípio, parece como o esperado. Um ponto de entrada, os métodos são chamados de estática. Evento sem parâmetros, evento com atributos. Verificamos se temos plugins, pressionamos um plug-in vazio para fazer algum tipo de "execução a seco". Ou adicione alguns outros plugins, exiba-os e assim por diante. Em geral, casos de usuário padrão, espero que tudo esteja claro até agora.
Agora vamos ver o que acontece em Java quando fazemos o mesmo:
private static void useAnalytics() { Analytics.INSTANCE.send(new Event("only_name_event", Collections.emptyMap())); final Map<String, Object> props = new HashMap<>(); props.put(USER_ID, 1235); props.put("my_custom_attr", true); Analytics.INSTANCE.send(new Event("custom_event", props)); boolean hasPlugins = Analytics.INSTANCE.getHasPlugins(); Analytics.INSTANCE.addPlugin(Analytics.INSTANCE.getEMPTY_PLUGIN());
A confusão com o INSTANCE imediatamente surge nos meus olhos, o que é esticado, a presença de valores explícitos para o parâmetro padrão com atributos, alguns getters com nomes idiotas. Como nós, em geral, nos reunimos aqui para transformar isso em algo semelhante ao arquivo anterior do Kotlin, passemos a cada momento que não gostamos e tentamos adaptá-lo de alguma forma.
Vamos começar com o evento. Removemos o parâmetro Colletions.emptyMap () da segunda linha e um erro do compilador é exibido. Qual o motivo disso?
data class Event( val name: String, val context: Map<String, Any> = emptyMap() )
Nosso construtor tem um parâmetro padrão para o qual passamos o valor. Como passamos de Java para Kotlin, é lógico supor que a presença de um parâmetro padrão gera dois construtores: um completo com dois parâmetros e um parcial, para o qual apenas o nome pode ser especificado. Obviamente, o compilador não pensa assim. Vamos ver por que ele acha que estamos errados.
Nossa principal ferramenta para analisar todas as reviravoltas de como o Kotlin se transforma em um bytecode da JVM - Kotlin Bytecode Viewer. No Android Studio e no IntelliJ IDEA, ele está localizado no menu Ferramentas - Kotlin - Mostrar código de bytes do Kotlin. Você pode simplesmente pressionar Cmd + Shift + A e digitar Kotlin Bytecode na barra de pesquisa.

Aqui, surpreendentemente, vemos um bytecode do que nossa classe Kotlin está se transformando. Não espero que você tenha um excelente conhecimento de bytecode e, o mais importante, os desenvolvedores de IDE também não o esperam. Portanto, eles fizeram um botão Descompilar.
Após clicar nele, vemos um código Java tão bom:
public final class Event { @NotNull private final String name; @NotNull private final Map context; @NotNull public final String getName() { return this.name; } @NotNull public final Map getContext() { return this.context; } public Event(@NotNull String name, @NotNull Map context) { Intrinsics.checkParameterIsNotNull(name, "name"); Intrinsics.checkParameterIsNotNull(context, "context"); super(); this.name = name; this.context = context; }
Vemos nossos campos, getters, o construtor esperado com dois parâmetros nome e contexto, tudo acontece bem. E abaixo vemos o segundo construtor, e aqui está uma assinatura inesperada: não com um parâmetro, mas por alguma razão com quatro.
Aqui você pode ficar envergonhado, mas pode subir um pouco mais fundo e vasculhar. Começando a entender, entenderemos que DefaultConstructorMarker é uma classe privada da biblioteca padrão Kotlin, adicionada aqui para que não haja conflitos com os construtores escritos, pois não podemos definir o parâmetro do tipo DefaultConstructorMarker com nossas mãos. E o interessante sobre int var3 é a máscara de bit de quais valores padrão devemos usar. Nesse caso, se a máscara de bits corresponder aos dois, sabemos que var2 não está definido, nossos atributos não estão definidos e usamos o valor padrão.
Como podemos resolver a situação? Para fazer isso, há uma anotação milagrosa @JvmOverloads do pacote que eu já falei. Temos que pendurá-lo no construtor.
data class Event @JvmOverloads constructor( val name: String, val context: Map<String, Any> = emptyMap() )
E o que ela vai fazer? Vamos passar para a mesma ferramenta. Agora vemos o construtor completo e o construtor com DefaultConstructorMarker e, lo e eis que, um construtor com um parâmetro, que agora está disponível em Java:
@JvmOverloads public Event(@NotNull String name) { this.name, (Map)null, 2, (DefaultConstructorMarker)null); }
E, como você pode ver, ele delega todo o trabalho com parâmetros padrão ao nosso construtor com máscaras de bits. Portanto, não produzimos informações sobre o valor padrão que precisamos colocar lá, apenas delegamos tudo em um construtor. Nice. Verificamos o que obtemos do lado do Java: o compilador está feliz e não indignado.
Vamos ver o que não gostamos a seguir. Não gostamos deste INSTANCE, que na IDEA é insensível em roxo. Eu não gosto de cor roxa :)

Vamos verificar, devido ao que acontece. Vejamos o bytecode novamente.
Por exemplo, destacamos a função init e garantimos que o init seja realmente gerado não estático.

Ou seja, o que quer que se diga, precisamos trabalhar com uma instância dessa classe e chamar esses métodos. Mas podemos forçar a geração de todos esses métodos a ser estática. Há uma anotação maravilhosa @JvmStatic para isso. Vamos adicioná-lo ao init e enviar funções e verificar o que o compilador pensa sobre isso agora.
Vemos que a palavra-chave estática foi adicionada ao init público final () e nos salvamos de trabalhar com o INSTANCE. Vamos verificar isso no código Java.
O compilador agora nos diz que estamos invocando o método estático no contexto INSTANCE. Isso pode ser corrigido: pressione Alt + Enter, selecione “Cleanup Code” e pronto, INSTANCE desaparece, tudo parece o mesmo que no Kotlin:
Analytics.send(new Event("only_name_event"));
Agora temos um esquema para trabalhar com métodos estáticos. Adicione esta anotação onde for importante para nós:

E o comentário: se os métodos que temos são obviamente os métodos de instância, então, por exemplo, com propriedades, nem tudo é tão óbvio. Os próprios campos (por exemplo, plug-ins) são gerados como estáticos. Mas getters e setters funcionam como métodos de instância. Portanto, para propriedades, você também precisa adicionar esta anotação para tornar os setters e getters como estáticos. Por exemplo, vemos a variável isInited, adicionamos a anotação @JvmStatic a ela e agora vemos no Kotlin Bytecode Viewer que o método isInited () se tornou estático, está tudo bem.
Agora vamos ao código Java, "para limpeza", e tudo se parece com Kotlin, exceto os pontos e vírgulas e a palavra nova - bem, você não se livra deles.
public static void useAnalytics() { Analytics.send(new Event("only_name_event")); final Map<String, Object> props = new HashMap<>(); props.put(USER_ID, 1235); props.put("my_custom_attr", true); Analytics.send(new Event("custom_event", props)); boolean hasPlugins = Analytics.getHasPlugins(); Analytics.addPlugin(Analytics.INSTANCE.getEMPTY_PLUGIN());
Próximo passo: vemos este getHasPlugins getter com o mesmo nome, com dois prefixos ao mesmo tempo. Claro, eu não sou um grande conhecedor da língua inglesa, mas parece-me que algo mais estava implícito aqui. Por que isso está acontecendo?
Como eles sabiam de perto com o Kotlin, os nomes de propriedades para getters e setters são gerados de acordo com as regras do JavaBeans. Isso significa que os getters geralmente estarão com prefixos get, setters com prefixos definidos. Mas há uma exceção: se você tiver um campo booleano e seu nome tiver o prefixo is, o getter será prefixado com is. Isso pode ser visto no exemplo do campo isInited acima.
Infelizmente, longe de sempre, os campos booleanos devem ser chamados através de is. O isPlugins não satisfaria exatamente o que queremos mostrar semanticamente pelo nome. Como estamos?
E não é difícil para nós, pois essa é a nossa própria anotação (como você já entendeu, muitas vezes repetirei isso hoje). A anotação @JvmName permite que você especifique qualquer nome que desejar (naturalmente suportado por Java). Adicione:
@JvmStatic val hasPlugins @JvmName("hasPlugin") get() = plugins.isNotEmpty()
Vamos verificar o que obtivemos em Java: o método getHasPlugins não está mais lá, mas o hasPlugins é algo para si. Isso resolveu nosso problema, novamente, com uma anotação. Agora resolvemos todas as anotações!
Como você pode ver, aqui colocamos a anotação diretamente no getter. Qual o motivo disso? Com o fato de que sob a propriedade há muito, e não está claro a que @JvmName se aplica. Se você transferir a anotação para o próprio val hasPlugins, o compilador não entenderá no que aplicá-la.
No entanto, o Kotlin também tem a capacidade de especificar onde as anotações são usadas diretamente nele. Você pode especificar o getter de destino, o arquivo inteiro, o parâmetro, o delegado, o campo, as propriedades, as funções de extensão do receptor, o setter e o parâmetro setter. No nosso caso, getter é interessante. E se você fizer isso, terá o mesmo efeito de quando suspendemos a anotação em get:
@get:JvmName("hasPlugins") @JvmStatic val hasPlugins get() = plugins.isNotEmpty()
Portanto, se você não tiver um getter personalizado, poderá anexá-lo diretamente à sua propriedade e tudo ficará bem.
O próximo ponto que nos confunde um pouco é "Analytics.INSTANCE.getEMPTY_PLUGIN ()". Aqui o assunto não está mais em inglês, mas simplesmente: POR QUE? A resposta é a mesma, mas primeiro uma pequena introdução.
Para tornar um campo constante, você tem duas maneiras. Se você definir uma constante como um tipo primitivo ou String, e também dentro do objeto, poderá usar a palavra-chave const e, em seguida, getter-setters e outras coisas não serão gerados. Será uma constante comum - estática final privada - e será sublinhada, ou seja, uma coisa Java absolutamente comum.
Mas se você quiser fazer uma constante a partir de um objeto diferente da string, não poderá usar a palavra const para isso. Aqui temos val EMPTY_PLUGIN = EmptyPlugin (), de acordo com ele, que terrível getter foi obviamente gerado. Podemos renomear @JvmName com uma anotação, remover esse prefixo get, mas ainda assim é um método - entre colchetes. Portanto, soluções antigas não funcionarão, estamos procurando novas.
E aqui para isso a anotação @JvmField, que diz: "Não quero getters aqui, não quero setters, faça de mim um campo". Coloque-o na frente de val EMPTY_PLUGIN e verifique se tudo está correto.

O Kotlin Bytecode Viewer mostra a peça destacada na qual você está atualmente no arquivo. Agora estamos em EMPTY_PLUGIN, e você vê que aqui algum tipo de inicialização está escrito no construtor. O fato é que o getter não está mais lá e o acesso a ele é apenas para gravação. E se você clicar em descompilar, veremos que “public static final EmptyPlugin EMPTY_PLUGIN” apareceu, foi exatamente isso que alcançamos. Nice. Verificamos que tudo agrada a todos, em particular ao compilador. A coisa mais importante que você precisa apaziguar é o compilador.
Genéricos
Vamos dar uma pausa no código e ver os genéricos. Este é um tópico bastante interessante. Ou escorregadio, que não gosta mais disso. Java tem suas próprias complexidades, mas o Kotlin é diferente. Antes de tudo, estamos preocupados com a variação. O que é isso
Variabilidade é uma maneira de transferir informações sobre uma hierarquia de tipos de tipos básicos para derivadas, por exemplo, para contêineres ou genéricos. Aqui temos as classes Animal e Dog com uma conexão muito óbvia: Dog é um subtipo, Animal é um subtipo, a flecha vem do subtipo.

E que conexão terão seus derivados? Vejamos alguns casos.
O primeiro é o iterador. Para determinar o que é um subtipo e o que é um subtipo, seremos guiados pela regra de substituição Barbara Liskov. Ele pode ser formulado da seguinte forma: "o subtipo não deve exigir mais e fornecer menos".
Em nossa situação, a única coisa que o Iterator faz é fornecer objetos digitados, por exemplo, Animal. Se aceitarmos o Iterator em algum lugar, podemos colocar o Iterator lá e obter Animal do próximo método (), porque o cachorro também é Animal. Nós fornecemos não menos, mas mais, porque um cachorro é um subtipo.

Repito: estamos apenas lendo esse tipo, portanto, a relação entre o tipo e o subtipo é preservada aqui. E esses tipos são chamados covariantes.
Outro caso: ação. Ação é uma função que não retorna nada, usa um parâmetro e só escrevemos em Ação, ou seja, tira um cachorro ou animal de nós.

Assim, aqui não fornecemos mais, mas exigimos, e não devemos exigir mais. Isso significa que nossa dependência está mudando. "Não mais", temos Animal (Animal menos que um cachorro). E esses tipos são chamados de contravariantes.
Há um terceiro caso - por exemplo, ArrayList, do qual lemos e escrevemos. Portanto, neste caso, violamos uma das regras, exigimos mais para um registro (um cachorro, não um animal). Esses tipos não estão relacionados de forma alguma e são chamados invariantes.

Assim, em Java, quando foi projetado antes da versão 1.5 (onde os genéricos apareciam), por padrão eles criavam matrizes covariantes. Isso significa que você pode atribuir uma matriz de seqüências de caracteres à matriz de objetos, passá-la para o método em que a matriz de objetos é necessária e tentar empurrar o objeto para lá, embora essa seja uma matriz de seqüências de caracteres. Tudo vai cair para você.
Tendo aprendido com a amarga experiência que isso não pode ser feito, ao projetar genéricos, eles decidiram "tornaremos as coleções invariáveis, não faremos nada com elas".
E, no final, acontece que, de uma maneira aparentemente óbvia, tudo deve ficar bem, mas na verdade não está bem:
Mas precisamos determinar de alguma forma o que, afinal, podemos: se estamos apenas lendo esta folha, por que não possibilitar a transferência da lista de cães aqui? Portanto, é possível caracterizar com curinga que tipo de variação esse tipo terá:
List<Dog> dogs = new ArrayList<>(); List<? extends Animal> animals = dogs;
Como você pode ver, essa variação é indicada no local de uso, onde atribuímos os cães. Portanto, isso é chamado de variação do site de uso.
Quais são as desvantagens disso? O lado negativo é que você deve especificar esses curingas assustadores sempre que usar sua API, e tudo isso é muito proveitoso no código. Mas no Kotlin, por algum motivo, isso funciona imediatamente e você não precisa especificar nada:
val dogs: List<Dog> = ArrayList() val animals: List<Animal> = dogs
Qual o motivo disso? Com o fato de que as folhas são realmente diferentes. Listar em Java significa escrever, enquanto no Kotlin é somente leitura, não implica. Portanto, em princípio, podemos dizer imediatamente que estamos apenas lendo daqui, portanto podemos ser covariantes. E isso é definido precisamente na declaração de tipo com a palavra-chave out substituindo o curinga:
interface List<out E> : Collection<E>
Isso é chamado de variação do site de declaração. Assim, indicamos tudo em um só lugar e, onde o usamos, não tocamos mais neste tópico. E isso é nishtyak.
Voltar ao código
Vamos voltar às nossas profundezas. Aqui temos o método addPlugins, é preciso uma lista:
@JvmStatic fun addPlugins (plugs: List<Plugin>) { plugs.forEach { addPlugin(it) } } , , List<EmptyPlugin>, , : <source lang="java"> final List<EmptyPlugin> pluginsToSet = Arrays.asList(new LoggerPlugin("Alog"), new SegmentPlugin());
Devido ao fato de a Lista no Kotlin ser covariante, podemos facilmente passar a lista de herdeiros de plug-ins aqui. Tudo vai funcionar, o compilador não se importa. Porém, devido ao fato de termos uma variação do site de declaração em que especificamos tudo, não podemos controlar a conexão com Java no estágio de uso. Mas o que acontece se realmente queremos uma folha de plug-in lá, não queremos herdeiros lá? Não há modificadores para isso, mas o que? É isso mesmo, há uma anotação. E a anotação é chamada @JvmSuppressWildcards, ou seja, por padrão, pensamos que aqui é um tipo com curinga, o tipo é covariante.
@JvmStatic fun addPlugins(plugs: List<@JvmSuppressWildcards Plugin>) { plugs.forEach { addPlugin(it) } }
Falando SuppressWildcards, suprimimos todas essas perguntas e nossa assinatura realmente muda. Mais do que isso, mostrarei como tudo fica no bytecode:

Vou remover a anotação do código por enquanto. Aqui está o nosso método. Você provavelmente sabe que existe apagamento de tipo. E no seu bytecode, não há informações sobre que tipo de perguntas havia, bem, genéricos em geral. Mas o compilador segue isso e assina nos comentários para o bytecode: e este é o tipo com a pergunta.

Agora, novamente inserimos a anotação e vemos que esse é o nosso tipo sem questionar.

Agora, nosso código anterior para de compilar com precisão porque cortamos os curingas. Você pode ver por si mesmo.
Nós criamos tipos covariantes. Agora o inverso é verdadeiro.
Acreditamos que a Lista tem uma pergunta. , getPlugins, . ? , , , . , Java.
final List<Plugin> plugins = Analytics.getPlugins(); displayPlugins(plugins); Analytics.getPlugins().add(new EmptyPlugin());
, - , , - . , . , - .
. Kotlin , , , , , wildcards Java. , , . , List, Plugin. , , , : Plugin, .
. , , usecase, - , .
, , , - . , Java. Kotlin List — read only-, , Java — ? , List wildcard. , . @JvmWildcard : , . , Java . Java « ?»:

List<? extends Plugin>, « ?» , , . script kiddie, « , , , ArrayList, ». , ArrayList , .
((ArrayList<Plugin>) Analytics.getPlugins()).add(new EmptyPlugin());
, , , defensive-, - . , , , script kiddies .
@JvmStatic fun getPlugins(): List<@JvmWildcard Plugin> = plugin.toImmutableList()
, @JvmSuppressWildcard , , , , .
, . , : .
Java. , :
@Override public void send(@NotNull Event event) throws IOException
:
interface Plugin { fun init() fun send(event: Event)
Kotlin checked exception. : . , , . Java -. : « Throws - , »:

-, Kotlin? , …
@Throws, . throws- . , IOExeption:
open class EmptyPlugin : Plugin { @Throws(IOException::class) override fun send(event: Event) {}
:
interface Plugin { fun init() @Throws(IOException::class) fun send(event: Event)
? , Java, exception, . , . , - , , @JvmName. .
, Java . …
package util fun List<Int>.printReversedSum() { println(this.foldRight(0) { it, acc -> it + acc }) } @JvmName("printReversedConcatenation") fun List<String>.printReversedSum() { println(this.foldRight(StringBuilder()) { it, acc -> acc.append(it) }) }
, Java , . , IDE . , ? , , , List, List. , type erasure. :

, , top-level c. printReversedSum List, List. Kotlin- , Java- . , kotlin.jvm , Java , , Kotlin . — , concatenation — , .
. . extension- reverse.
inline fun String.reverse() = StringBuilder(this).reverse().toString() inline fun <reified T> reversedClassName() = T::class.java.simpleName.reverse() inline fun <T> Iterable<T>.forEachReversed(action: (T) -> Unit) { for (element in this.reversed()) action(element) }
reverse , ReverserKt.
private static void useUtils() { System.out.println(ReverserKt.reverse("Test")); SumsKt.printReversedSum(asList(1, 2, 3, 4, 5)); SumsKt.printReversedConcatenation(asList("1", "2", "3", "4", "5")); }
, , . , , Java, - . . ? , @JvmName, , .
, , , , , .
@file:Suppress("NOTHING_TO_INLINE") @file:JvmName("ReverserUtils")
Java ReverserKt, , ReverserUtils . « 2.1» — , top-level , . , , sums.kt SumsKt, , reversing ReverserUtils. @JvmName, «ReverserUtils», , , , .
, , « , -». ? @JvmMultifileClass, , , .
"@file:JvmMultifileClass", SumsKt ReverserUtils, — . !
, . , , . , , , @JvmName Kotlin.
Kotlin-
, , . , Kotlin- .
, inline-. Kotlin , , Java ? , , , Java. , , Kotlin-only , dex count limit. Kotlin , .
Reified type parameters. Kotlin, - , Java . Kotlin-only , Kotlin, Java reified, .
java.lang.Class. , Java, . . « Retrofit», ( , ):
class Retrofit private constructor( val baseUrl: String, val client: Client ) { fun <T : Any> create(service: Class<T>): T {...} fun <T : Any> create(service: KClass<T>): T { return create(service.java) } }
, Java, , KClass, , extension-, KClass Class, Class KClass ( Kotlin, ).
, . Kotlin- KClass, Reified-, :
inline fun <reified T : Any> create(): T { return create(T::class.java.java)
. Kotlin , .
val api = retrofit.create(Api::class) val api = retrofit.create<Api>() , ::class . Reified-, -.
Unit. Unit, , void Java, . . , . - Scala, Scala , - , , , void.
Kotlin . Kotlin 22 , - . , , Unit, void, Unit. . , Unit? , . .
inline fun <T> Iterable<T>.forEachReversed(action: (T) -> Unit) { for (element in this.reversed()) action(element) }
Kotlin: , method reference, , , .
private fun useMisc() { listOf(1, 2, 3, 4).forEachReversed(::println) println(reversedClassName<String>()) }
Java? Java :
private static void useMisc() { final List<Integer> list = asList(1, 2, 3, 4); ReverserUtils.forEachReversed(list, integer -> { System.out.println(integer); return Unit.INSTANCE; });
- , - . Void , . , void, . , , , . , Unit . null, . , .
: Typealiases — , , Kotlin, Java, , , . , - . Java- .
: visibility. , internal visibility. , Kotlin package private, - , public. internal. Internal — , . Retrofit internal- validate.
internal fun validate(): Retrofit { println("!!!!!! internal fun validate() was called !!!!!!") return this }
Kotlin, . Java? validate? , , internal public. , Kotlin bytecode viewer.

public, , , , , , API . - 80 , .
Java :
final Api api = retrofit .validate$production_sources_for_module_library_main() .create(Api.class); api.sendMessage("Hello from Java"); }
. , , , . , let me explain this to you. , ?
final Api api = retrofit .validate$library() .create(Api.class); api.sendMessage("Hello from Java"); }
. « ?» … MAGIC!
, - internal, , API. script kiddie Kotlin Bytecode Viewer, . internal visibility.
, . , ,
, , SkillsMatter. .
, Kotlin-. , - , . Kotlin bytecode viewer .
Obrigada
, : 8-9 Mobius , . — , .