
Este artigo irá falar sobre o uso de estática no Kotlin.
Vamos começar.
Kotlin não tem estática!
Isto é afirmado na documentação oficial.
E parece que isso poderia terminar o artigo. Mas deixe-me, como assim? Afinal, se no Android Studio você inserir código Java em um arquivo Kotlin, o conversor inteligente fará a mágica, transformará tudo em código no idioma certo e funcionará! Mas e a compatibilidade total com Java?
Nesse momento, qualquer desenvolvedor, aprendendo sobre a falta de estática no Kotlin, entrará na documentação e nos fóruns para lidar com esse problema. Vamos nos reunir, pensativa e meticulosamente. Tentarei manter o mínimo de perguntas possível até o final deste artigo.
Qual é a estática em Java? Existem:
- campos estáticos de classe
- métodos de classe estática
- classes aninhadas estáticas
Vamos fazer um experimento (esta é a primeira coisa que vem à mente).
Crie uma classe Java simples:
public class SimpleClassJava1 { public static String staticField = "Hello, static!"; public static void setStaticValue (String value){ staticField = value; } }
Tudo é fácil aqui: na classe, criamos um campo estático e um método estático. Fazemos tudo publicamente para experimentos com acesso externo. Conectamos o campo e o método logicamente.
Agora crie uma classe Kotlin vazia e tente copiar todo o conteúdo da classe SimpleClassJava1 para ela. Respondemos "sim" à pergunta resultante sobre conversão e vemos o que aconteceu:
class SimpleClassKotlin1 { var staticField = "Hello, static!" fun setStaticValue(value: String) { staticField = value } }
Parece que isso não é exatamente o que precisamos ... Para garantir isso, converteremos o bytecode dessa classe em código Java e veremos o que aconteceu:
public final class SimpleClassKotlin1 { @NotNull private String staticField = "Hello, static!"; @NotNull public final String getStaticField() { return this.staticField; } public final void setStaticField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.staticField = var1; } public final void setStaticValue(@NotNull String value) { Intrinsics.checkParameterIsNotNull(value, "value"); this.staticField = value; } }
Sim Tudo está exatamente como parecia. Não tem cheiro de estática aqui. O conversor simplesmente cortou o modificador estático na assinatura, como se não estivesse lá. Por via das dúvidas, chegaremos imediatamente a uma conclusão: não confie cegamente no conversor, às vezes ele pode trazer surpresas desagradáveis.
A propósito, cerca de seis meses atrás, a conversão do mesmo código Java para Kotlin teria mostrado um resultado ligeiramente diferente. Mais uma vez: tenha cuidado com a conversão automática!
Nós experimentamos mais.
Vamos a qualquer classe no Kotlin e tentamos chamar os elementos estáticos da classe Java:
SimpleClassJava1.setStaticValue("hi!") SimpleClassJava1.staticField = "hello!!!"
Aqui está como! Tudo é perfeitamente chamado, até o preenchimento automático do código nos diz tudo! Muito curioso.
Agora vamos para a parte mais substancial. De fato, os criadores de Kotlin decidiram se afastar da estática na forma em que estamos acostumados a usá-la. Por que fizemos exatamente isso e não discutiremos o contrário - há muitas disputas e opiniões sobre esse assunto na rede. Vamos descobrir como viver com isso. Naturalmente, não fomos privados apenas de estática. Kotlin nos fornece um conjunto de ferramentas com as quais podemos compensar os perdidos. Eles são adequados para uso interno. E a prometida compatibilidade total com o código Java. Vamos lá!
A coisa mais rápida e fácil que você pode perceber e começar a usar é a alternativa que nos é oferecida, em vez de métodos estáticos - funções no nível do pacote. O que é isso Esta é uma função que não pertence a nenhuma classe. Ou seja, esse tipo de lógica que está no vácuo em algum lugar do espaço do pacote. Podemos descrevê-lo em qualquer arquivo dentro do pacote que nos interessar. Por exemplo, nomeie esse arquivo JustFun.kt e coloque-o no pacote
com.example.mytestapplication
package com.example.mytestapplication fun testFun(){
Converta o bytecode deste arquivo em Java e olhe dentro:
public final class JustFunKt { public static final void testFun() {
Vemos que em Java é criada uma classe cujo nome leva em consideração o nome do arquivo no qual a função é descrita e a própria função se transforma em um método estático.
Agora, se quisermos chamar a função
testFun
no Kotlin a partir de uma classe (ou a mesma função) localizada no
package com.example.mytestapplication
(ou seja, o mesmo pacote da função), podemos simplesmente acessá-la sem truques adicionais. Se o chamarmos de outro pacote, devemos importar, o que é familiar para nós e geralmente aplicável às classes:
import com.example.pavka.mytestapplication.testFun
Se falamos em chamar a função
estFun
partir do código Java, sempre precisamos importar a função, independentemente de qual pacote a chamamos:
import static com.example.pavka.mytestapplication.ForFunKt.testFun;
A documentação diz que, na maioria dos casos, em vez de métodos estáticos, basta usarmos as funções no nível do pacote. No entanto, na minha opinião pessoal (que não precisa coincidir com a opinião de todos os outros), esse método de implementação da estática é adequado apenas para pequenos projetos.
Acontece que essas funções não pertencem explicitamente a nenhuma classe. Visualmente, a chamada deles parece uma chamada para o método de classe (ou seu pai) no qual estamos localizados, o que às vezes pode ser confuso. Bem, e o principal - só pode haver uma função com esse nome no pacote. Mesmo se tentarmos criar a função com o mesmo nome em outro arquivo, o sistema nos dará um erro. Se falamos de grandes projetos, muitas vezes temos, por exemplo, diferentes fábricas com métodos estáticos com o mesmo nome.
Vejamos outras alternativas para implementar métodos e campos estáticos.
Lembre-se de qual é o campo estático de uma classe. Este é um campo de classe que pertence à classe na qual é declarado, mas não pertence a uma instância específica da classe, ou seja, é criado em uma única instância para toda a classe.
A Kotlin nos oferece para esses fins usar alguma entidade adicional, que também existe em uma única cópia. Em outras palavras, singleton.
Kotlin tem uma palavra-chave de objeto para declarar singletones.
object MySingltoneClass {
Tais objetos são inicializados preguiçosamente, ou seja, no momento da primeira chamada para eles.
Ok, existem singletones em Java também, onde estão as estatísticas?
Para qualquer classe no Kotlin, podemos criar um objeto complementar ou complementar. Um singleton vinculado a uma classe específica. Isso pode ser feito usando duas palavras-chave
companion object
juntas:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){
Aqui temos a classe
SimpleClassKotlin1
, na qual declaramos um singleton com a palavra-chave object e o vinculamos ao objeto dentro do qual é declarado com a palavra-chave complementar. Aqui você pode prestar atenção ao fato de que, diferentemente da declaração singleton anterior (MySingltoneClass), o nome da classe singleton não é indicado. Se o objeto for declarado complementar, é permitido não indicar seu nome. Em seguida, ele será automaticamente nomeado
Companion
. Se necessário, podemos obter uma instância da classe complementar desta maneira:
val companionInstance = SimpleClassKotlin1.Companion
No entanto, uma chamada para as propriedades e métodos de uma classe complementar pode ser feita diretamente, através de uma chamada para a classe à qual está anexada:
SimpleClassKotlin1.companionField SimpleClassKotlin1.companionFun("Hi!")
Já parece muito com a chamada de campos e classes estáticos, certo?
Se necessário, podemos dar um nome à classe associada, mas na prática isso raramente é feito. Entre os recursos interessantes das classes acompanhantes, pode-se notar que, como qualquer classe comum, ele pode implementar interfaces, o que às vezes pode nos ajudar a adicionar um pouco mais de ordem ao código:
interface FactoryInterface<T> { fun factoryMethod(): T } class SimpleClassKotlin1 { companion object : FactoryInterface<MyClass> { override fun factoryMethod(): MyClass = MyClass() } }
Uma classe complementar pode ter apenas uma classe. No entanto, ninguém nos proíbe de declarar qualquer número de objetos singleton dentro da classe, mas, neste caso, devemos especificar explicitamente o nome dessa classe e, portanto, indicar esse nome quando nos referirmos aos campos e métodos dessa classe.
Falando de classes declaradas como objeto, podemos dizer que também podemos declarar objetos aninhados nelas, mas não podemos declarar objetos complementares nelas.
É hora de olhar "por baixo do capô". Faça nossa aula simples:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){ } } object OneMoreObject { var value = 1 fun function(){ } }
Agora descompile seu bytecode em Java:
public final class SimpleClassKotlin1 { @NotNull private static String companionField = "Hello!"; public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null); public static final class OneMoreObject { private static int value; public static final SimpleClassKotlin1.OneMoreObject INSTANCE; public final int getValue() { return value; } public final void setValue(int var1) { value = var1; } public final void function() { } static { SimpleClassKotlin1.OneMoreObject var0 = new SimpleClassKotlin1.OneMoreObject(); INSTANCE = var0; value = 1; } } public static final class Companion { @NotNull public final String getCompanionField() { return SimpleClassKotlin1.companionField; } public final void setCompanionField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); SimpleClassKotlin1.companionField = var1; } public final void companionFun(@NotNull String vaue) { Intrinsics.checkParameterIsNotNull(vaue, "vaue"); } private Companion() { }
Nós olhamos o que aconteceu.
A propriedade do objeto complementar é representada como um campo estático da nossa classe:
private static String companionField = "Hello!";
Isso parece ser exatamente o que queríamos. No entanto, esse campo é privado e acessado por meio de getter e setter de nossa classe complementar, que é apresentada aqui como uma
public static final class
e sua instância é apresentada como uma constante:
public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null);
A função companionFun não se tornou o método estático da nossa classe (provavelmente não deveria). Permaneceu a função de um singleton inicializado na classe SimpleClassKotlin1. No entanto, se você pensar sobre isso, logicamente isso é a mesma coisa.
Com a classe
OneMoreObject
situação é muito semelhante. É importante observar apenas que aqui, diferentemente do companheiro, o campo da classe value não foi movido para a classe
SimpleClassKotlin1
, mas permaneceu no
OneMoreObject
, mas também se tornou estático e recebeu o getter e o setter gerados.
Vamos tentar compreender todas as opções acima.
Se queremos implementar campos estáticos ou métodos de classe no Kotlin, para isso devemos usar o objeto complementar declarado dentro desta classe.
Chamar esses campos e funções do Kotlin será exatamente o mesmo que chamar estática em Java. Mas e se tentarmos chamar esses campos e funções em Java?
O preenchimento automático informa que as seguintes chamadas estão disponíveis:
SimpleClassKotlin1.Companion.companionFun("hello!"); SimpleClassKotlin1.Companion.setCompanionField("hello!"); SimpleClassKotlin1.Companion.getCompanionField();
Ou seja, aqui não vamos indicar o nome do companheiro diretamente. Assim, o nome que foi designado ao objeto complementar padrão é usado aqui. Não é muito conveniente, certo?
No entanto, os criadores do Kotlin tornaram possível torná-lo mais familiar em Java. E há várias maneiras de fazer isso.
@JvmField var companionField = "Hello!"
Se aplicarmos esta anotação ao campo
companionField
do nosso objeto
companionField
, ao converter o bytecode em Java, veremos que o campo estático
companionField
SimpleClassKotlin1 não é mais privado, mas público, e o getter e setter para companionField desaparecerão na classe estática
Companion
. Agora podemos acessar o
companionField
partir do código Java da maneira usual.
A segunda maneira é especificar um modificador
lateinit
para propriedades do
lateinit
complementar, propriedades com inicialização tardia. Não esqueça que isso se aplica apenas às propriedades var, e seu tipo deve ser não nulo e não deve ser primitivo. Bem, não se esqueça das regras para lidar com essas propriedades.
lateinit var lateinitField: String
E mais uma maneira: podemos declarar a propriedade do objeto complementar uma constante, especificando o modificador const. É fácil adivinhar que isso deve ser uma propriedade val.
const val myConstant = "CONSTANT"
Em cada um desses casos, o código Java gerado conterá o campo estático público habitual; no caso de const, esse campo também será final. Obviamente, vale a pena entender que cada um desses 3 casos tem seu próprio objetivo lógico, e apenas o primeiro deles foi projetado especificamente para facilitar o uso com Java, o restante recebe esse "coque" como se estivesse em uma carga.
Deve-se observar separadamente que o modificador const pode ser usado para propriedades de objetos, objetos complementares e propriedades do nível do pacote. No último caso, obtemos o mesmo que usar as funções no nível do pacote e com as mesmas restrições. O código Java é gerado com um campo público estático na classe, cujo nome leva em consideração o nome do arquivo no qual descrevemos a constante. Um pacote pode ter apenas uma constante com o nome especificado.
Se queremos que a função do objeto complementar também seja convertida em um método estático ao gerar código Java, para isso, precisamos aplicar a anotação
@JvmStatic
a essa função.
Também é permitido aplicar a anotação
@JvmStatic
às propriedades dos objetos complementares (e apenas objetos singleton). Nesse caso, a propriedade não se transformará em um campo estático, mas um getter e setter estáticos para essa propriedade serão gerados. Para uma melhor compreensão, veja esta classe Kotlin:
class SimpleClassKotlin1 { companion object{ @JvmStatic fun companionFun (vaue: String){ } @JvmStatic var staticField = 1 } }
Nesse caso, as seguintes chamadas são válidas do Java:
int x; SimpleClassKotlin1.companionFun("hello!"); x = SimpleClassKotlin1.getStaticField(); SimpleClassKotlin1.setStaticField(10); SimpleClassKotlin1.Companion.companionFun("hello"); x = SimpleClassKotlin1.Companion.getStaticField(); SimpleClassKotlin1.Companion.setStaticField(10);
As seguintes chamadas são válidas do Kotlin:
SimpleClassKotlin1.companionFun("hello!") SimpleClassKotlin1.staticField SimpleClassKotlin1.Companion.companionFun("hello!") SimpleClassKotlin1.Companion.staticField
É claro que para Java você deve usar os 3 primeiros e para o Kotlin os 2. primeiros. O restante das chamadas é válido.
Agora resta esclarecer o último. E as classes aninhadas estáticas? Tudo é simples aqui - o análogo dessa classe no Kotlin é uma classe aninhada regular sem modificadores:
class SimpleClassKotlin1 { class LooksLikeNestedStatic { } }
Depois de converter o bytecode em Java, vemos:
public final class SimpleClassKotlin1 { public static final class LooksLikeNestedStatic { } }
De fato, é disso que precisamos. Se não queremos que a classe seja final, no código Kotlin especificamos o modificador aberto para ela. Lembrei-me disso por precaução.
Eu acho que você pode resumir. De fato, na própria Kotlin, como foi dito, não há estática na forma em que estamos acostumados a vê-la. Mas o conjunto de ferramentas proposto nos permite implementar todos os tipos de estática no código Java gerado. Também é fornecida total compatibilidade com Java, e podemos chamar diretamente campos estáticos e métodos de classes Java do Kotlin.
Na maioria dos casos, a implementação de uma estatística no Kotlin requer mais algumas linhas de código. Talvez este seja um dos poucos, ou talvez o único caso em que você precise escrever mais em Kotlin. No entanto, você se acostuma rapidamente.
Penso que nos projetos em que os códigos Kotlin e Java são compartilhados, você pode abordar com flexibilidade a escolha da linguagem usada. Por exemplo, parece-me que o Java é mais adequado para armazenar constantes. Mas aqui, como em muitas outras coisas, vale a pena ser guiado pelo bom senso e pelas regras para escrever código no projeto.
E no final do artigo, aqui está essa informação. Talvez no futuro, o Kotlin ainda tenha um modificador estático que elimine muitos problemas e facilita a vida dos desenvolvedores, e o código é mais curto. Fiz essa suposição encontrando o texto apropriado no parágrafo 17 das
descrições dos recursos do
Kotlin .
É verdade que este documento data de maio de 2017 e no estaleiro já é o fim de 2018.
Isso é tudo para mim. Eu acho que o tópico foi resolvido com alguns detalhes. Perguntas escreva nos comentários.