Por que eu devo ler este artigo:Através de exemplos, apresentaremos como escapar de algumas armadilhas específicas da sintaxe dessa linguagem. Veremos também como interpretar e corrigir erros comuns emitidos pelo compilador, no que se refere a construtores e blocos de inicialização.

Kotlin: Classes

Artigos a Série Kotlin

Uma classe é um bloco de construção de software fundamental, encontrado na grande maioria das linguagens orientadas a objetos e, dessa forma, também presente em Kotlin. Elas são uma ferramenta da abstração, que usamos para criar tipos de dados compostos de métodos e variáveis, que podem possuir os seus próprios tipos.

Para aqueles que ainda não estão familiarizados com a orientação a objetos, nesse paradigma de desenvolvimento de software, em lugar de pensarmos em dados como fórmula, posologia, contraindicação, costumamos abstrair esses dados em classes. Sendo assim, criar uma classe é o primeiro passo dado após conhecer o conceito de abstração. Através da abstração, elegemos os dados relevantes a cerca de um objeto a fim de reduzir a sua complexidade e aumentar a sua eficiência. Com as classes, criamos os tipos aos quais esses objetos pertencem.

Declaração de classes

Em Kotlin, um arquivo pode conter diferentes declarações de classes, as quais são feitas utilizando a palavra reservada class. Abaixo apresentamos uma declaração de classe feita da forma mais simples possível em Kotlin.

class Medicamento

No exemplo acima, uma vez que a classe Medicamento não possui um corpo, podemos omitir as chaves que estariam na frente do seu nome.

Construtores

Kotlin distingue os construtores de uma classe entre primários e secundários. O construtor primário de uma classe faz parte do seu cabeçalho e pode conter apenas uma lista de parâmetros, assim como apresentado abaixo.

class Medicamento(val formula: String, val posologia: String)

Dado que o construtor primário não pode conter nenhum código, uma classe pode conter um ou mais blocos de inicialização, que são executados na ordem como são declarados no corpo da mesma. Nesses blocos de inicialização, conforme visto a seguir, podemos acessar quaisquer parâmetros que estejam presentes no construtor primário. A razão drelação entre o construtor primário e as propriedades de umisso será melhor detalhada em breve, quando abordarmos a a classe.

class Medicamento(val formula: String, val posologia: String) {
     init {
         require(formula.trim().length > 0) {
            "Informe uma fórmula"
         }

         require(posologia.trim().length > 0) {
            "Informe uma posologia"
         }
     }
}

Ao instanciar a classe Medicamento, informando um texto vazio para um dos seus parâmetros, devemos ver um erro gerado em tempo de execução e contendo a seguinte mensagem:

Exception in thread "main" java.lang.IllegalArgumentException: Informe uma fórmula
     at br.com.edrd.processing.Medicamento.(Classes.kt:5)
     at br.com.edrd.processing.ClassesKt.main(Classes.kt:13)

O construtor primário, caso utilize algum modificador de acesso ou anotações, deve empregar a palavra reservada constructor, assim como vemos abaixo.

class MedicamentoController @Autowired constructor(val repositorio: MedicamentoRepositorio)
Kotlin possui os modificadores de acesso public, private, protected e internal. Eles são cobertos em uma próxima seção neste artigo.

Além do construtor primário, uma classe também pode declarar um ou mais construtores secundários. Esses, por sua vez, sempre devem utilizar a palavra reservada constructor em suas declarações, mesmo quando um modificador de acesso for omitido ou nenhuma anotação for utilizada.

class Medicamento {
    constructor(formula: String, posologia: String)
}

No caso de uma classe possuir mais de um construtor, sendo um deles primário, cada construtor secundário deve delegar ao construtor primário. Quando ambos os construtores estiverem na mesma classe, a delegação de um construtor para o outro é feita com a palavra reservada this.

Considerando que a classe Medicamento possa ser inicializada com ou sem uma contraindicação, caso no qual o valor dessa propriedade passa a ser uma mensagem padrão, poderíamos modelá-la da seguinte forma:

class Medicamento(val formula: String, posologia: String, val contraindicacao: String) {

     constructor(formula: String, posologia: String): this(formula, posologia, 
     "Este medicamento não é indicado para pessoas alérgicas a $formula")
}

Construtores e blocos de inicialização

A delegação de um construtor secundário para o construtor primário ocorrerá como sendo a primeira instrução no construtor secundário. Isso quer dizer que, uma vez que eles passam a fazer parte do construtor primário, cada bloco de inicialização será executado antes de qualquer construtor secundário.

Fundamentalmente, caso uma classe não possua nenhum construtor e não seja abstrata, um construtor público vazio lhe será atribuído. Supondo que nessa mesma classe não exista um construtor primário declarado, a delegação ocorrerá de forma implícita, como demonstrado a seguir.

class Medicamento {
     val formula: String

     constructor(formula: String, posologia: String) {
         this.formula = formula

         println("Construtor secundário")
     }

     init {
        println("Bloco de inicialização")
     }
}

No exemplo acima, não importando a ordem da declaração do construtor secundário e do bloco de inicialização, as mensagens exibidas serão “Bloco de inicialização” e “Construtor secundário”, nessa ordem.

Construtores e propriedades

O construtor primário age de forma diferente do construtor secundário quanto a geração de propriedades para a classe na qual eles são declarados. Essa abordagem impede que as propriedades de uma classe variem de acordo com os parâmetros declarados nos construtores, uma vez que a geração de propriedades é limitada ao construtor primário de uma classe.

Para demonstrar isso observe a seguinte declaração da classe Medicamento:

class Medicamento {
    constructor(formula: String, posologia: String)
}

Diferentemente do construtor primário, os parâmetros do construtor secundário não gerarão propriedades, visto que ao tentar executar um código como este, será gerado um erro de compilação:

val medicamento: Medicamento = Medicamento("", "")
medicamento.formula

Error:(10, 17) Kotlin: Unresolved reference: formula

Sendo assim, nesse caso é obrigatório declarar uma propriedade na classe Medicamento, a qual poderá receber o valor do parâmetro do construtor e ser acessada a partir de uma instância, como visto acima.

Podemos eliminar o erro ao acessar a propriedade formula da seguinte forma:

class Medicamento {
     val formula: String

     constructor(formula: String, posologia: String) {
        this.formula = formula
     }
}

Assim, uma classe em Kotlin pode possuir propriedades declaradas em seu corpo explicitamente. Essas propriedades podem ser mutáveis, quando declaradas com a palavra reservada var, ou somente leitura, quando declaradas com a palavra reservada val. Caso elas sejam declaradas imutáveis, será necessário inicializá-las, o que pode ser feito no construtor secundário da classe, como no exemplo acima.

Imutabilidade dos parâmetros dos construtores secundário

Conforme demonstrado anteriormente, os parâmetros do construtor secundário são declarados sem utilizar as palavras reservadas val ou var, dessa maneira:

class Medicamento {
    constructor(formula: String, posologia: String)
}

Caso uma tentativa de fazer o contrário seja feita, digamos declarando formula como val formula: String, um erro de compilação será emitido com a seguinte mensagem:

Kotlin: 'val' on secondary constructor parameter is not allowed

Em Kotlin, tanto os parâmetros de funções quanto os de construtores são imutáveis por definição, o que elimina a necessidade de se utilizar val ou var nesse caso.

Em adendo, a utilização de val ou var em construtores é restrita ao construtor primário de uma classe para definir a mutabilidade das suas propriedades, será imutável ou não, dado que apenas ele pode gerar propriedades para uma classe.

Por exemplo, digamos que a fórmula de um medicamento nunca possa alterada, uma vez que seja definida no momento da criação de uma instância, mas que a sua posologia depende de uma regra externa a essa classe. Dessa forma, podemos modelar a classe Medicamento conforme apresentado a seguir.

class Medicamento(val formula: String, var posologia: String)

Tendo sido o parâmetro formula declarado imutável, a propriedade homônima da classe Medicamento também será somente leitura. Ao tentar atribuir valor a ela a partir de uma instância o seguinte erro será apresentado:

Error:(8, 5) Kotlin: Val cannot be reassigned

O mesmo erro não ocorrerá ao atribuir valor a propriedade posologia a partir de uma instância da classe Medicamento, uma vez que o parâmetro posologia, presente no construtor primário, foi declarado mutável.

Níveis de acesso

Em Kotlin, não informar um nível de acesso para um tipo faz com que o modificador public seja automaticamente aplicado. Além desse, geralmente não utilizado devido a redundância que causa, Kotlin possui três níveis de acesso. Dentre eles, Protected pode ser utilizado apenas por classes aninhadas. Quando utilizado em nível de arquivo, um erro será emitido pelo compilador.

protected class Medicamento constructor(val formula: String, var posologia: String)

Error:(3, 1) Kotlin: Modifier 'protected' is not applicable inside 'file'

No caso abaixo utilizar o modificador protected é sintaticamente permitido porque Tributacao não está em nível de arquivo e é uma classe aninhada em Medicamento.

class Medicamento {

    protected class Tributacao
}

Private é um nível de acesso que restringe o escopo de utilização da classe apenas ao arquivo no qual ela foi declarada.

Ao tentar utilizar uma classe private fora do arquivo no qual ela foi declarado, um erro será emitido pelo compilador prematuramente, momento da sua importação.

private class Medicamento constructor(val formula: String, var posologia: String)

import br.com.devmedia.kotlin.a.Medicamento

fun main(args: Array) {
     println(Medicamento("C8H9NO2", "...12 anos ou mais variam de 500 a 1000 mg/
     dose com intervalos de 4 a 6 horas..."))
}

Error:(3, 33) Kotlin: Cannot access 'Medicamento': it is private in file
Em Kotlin, a declaração de um pacote é feita com a palavra reservada package, seguida do nome do pacote. A importação de uma classe de outro pacote é feita com a palavra import, seguida do nome completo da classe.

O terceiro modificador de acesso é internal, que permite criar uma instância da classe em qualquer lugar no módulo no qual ela foi declarado. Para o Kotlin um módulo é um conjunto de fontes compilados juntos. Isso torna esse comportamento difícil de ser verificado em um mesmo módulo do Intellij IDEA, projeto do Maven, Ant task, etc.

Conclusão

Kotlin é uma linguagem orientada a objetos e funcional, que ganhou maior popularidade ao ser anunciada como uma alternativa ao Java para desenvolvimento para Android, na I/O Conference de 2017. Desde então, diversos projetos como Kotlin Native, Coroutines e Kotlin to JavaScript têm sido alvo do interesse de diferentes comunidades de programadores. Independe de qual seja o propósito para o qual você pretende utilizar o Kotlin, será necessário conhecer a sintaxe dessa linguagem, que é concisa e, ao mesmo tempo, repleta de recursos sintáticos.

Confira também