🔗 Tarefa
Reescreva o seguinte código para Kotlin:
Java
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
JavaScript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
return this.name;
}
getAge() {
return this.age;
}
}
TypeScript
class Person {
readonly name: string;
readonly age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
getName(): string {
return this.name;
}
getAge(): number {
return this.age;
}
}
Python
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
Swift
struct Person {
let name: String
let age: Int
}
PHP
class Person {
public function __construct(private string $name, private int $age) {}
public function getName(): string {
return $this->name;
}
public function getAge(): int {
return $this->age;
}
}
Dart
class Person {
final String name;
final int age;
Person(this.name, this.age);
}
Go
type Person struct {
Name string
Age int
}
func NewPerson(name string, age int) *Person {
return &Person{Name: name, Age: age}
}
C#
public class Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Em seguida, adicione o modificador data
à classe resultante.
Introdução às Classes em Kotlin
Em programação, uma classe é um modelo a partir do qual os objetos são criados, gerando uma instância daquela classe. As classes encapsulam dados para o objeto e métodos para manipular esses dados.
Kotlin, como uma linguagem de programação orientada a objetos, permite a definição e uso de classes, com algumas particularidades:
1. Classes Simples
Uma classe em Kotlin pode ser declarada usando a palavra-chave class
. Se uma classe não tiver corpo, você pode omitir as chaves { }
.
class Carro
2. Métodos
São funções definidas em uma classe que operam sobre os dados da classe.
class Calculadora {
fun somar(a: Int, b: Int): Int {
return a + b
}
}
3. Construtores
Kotlin tem uma sintaxe concisa para declarar construtores diretamente nos cabeçalhos das classes. O código abaixo declara uma classe com um construtor primário, mas os parâmetros deste construtor não são automaticamente transformados em propriedades da classe:
class Pessoa(nome: String, idade: Int) {
fun imprimirIdade() {
// Não é possível, já que "idade" não é atributo de class
// println("Idade: $idade")
}
}
4. Propriedades
Podemos declarar propriedades diretamente no construtor primário, usando os modificadores val
e var
.
Quando fazemos isso, estamos não apenas declarando um construtor, mas também definindo propriedades para a classe:
class Pessoa(val nome: String, var idade: Int) {
fun imprimeNome() {
println(nome)
}
}
5. Modificadores de acesso
Em Kotlin, classes, objetos, propriedades e funções têm um modificador de acesso public
por padrão, o que significa que podem ser acessados a partir de qualquer outro código. Porém, é possível limitar o acesso utilizando as seguintes palavras reservadas:
-
private
: visível apenas dentro do arquivo Kotlin onde está declarado. -
protected
: não é permitido para classes de nível superior, mas para propriedades/métodos, eles são visíveis na classe e em suas subclasses. -
internal
: visível em todos os arquivos no mesmo módulo.
6. Especialidades de classes
-
Classes Internas (
inner
): são classes definidas dentro de outra classe com acesso aos membros da classe externa. -
Classes Abertas (
open
): Em Kotlin, por padrão, todas as classes são finais (não podem ser herdadas). Se você quiser permitir que uma classe seja herdada, você deve marcar a classe comopen
. -
Classes Abstratas (
abstract
): São classes que não podem ser instanciadas direta e geralmente servem como base para outras classes. -
Classes Seladas (
sealed
): Iremos aprender no próximo modulo 🔗 Classes seladas (Sealed classes) - Data Classes: Classes otimizadas para armazenar dados, que serão mais exploradas no decorrer desse texto.
🚫 Nota Importante: As data classes
em Kotlin não podem ser marcadas como abertas (open
), abstratas (abstract
), seladas (sealed
) ou internas (inner
).
Dado esse contexto, podemos explorar mais sobre as Data Classes em Kotlin 🧵👇
Caso de uso
As Data Classes no Kotlin são uma maneira concisa de criar classes que apenas mantêm dados. Elas automaticamente fornecem métodos úteis, como equals()
, hashCode()
, toString()
, copy()
e .componentN()
. Isso ajuda a reduzir a verbosidade do código
As data classes são uma parte muito útil e poderosa do Kotlin, sendo usadas frequentemente para casos em que você precisa armazenar dados, mas não precisa de muita lógica ou comportamento adicional na classe.
data class Pessoa(
val nome: String,
val idade: Int,
)
Funções Especiais das Data Classes
Apenas adicionando a palavra reservada data
logo antes de class
, as seguintes funções especiais são automaticamente disponíveis
1. equals()
Esta função verifica a igualdade estrutural dos dados na classe. No exemplo, carlo == luiza
usa internamente a
função equals()
e retorna false
porque Carlo e Luiza têm dados diferentes, mesmo que tenham a mesma idade.
val pessoaA = Pessoa("Ricardo", 35)
val pessoaB = Pessoa("Ricardo", 35)
val pessoaC = Pessoa("Carla", 28)
println(pessoaA == pessoaB) // true, pois têm o mesmo nome e a mesma idade
println(pessoaA == pessoaC) // false, pois os nomes e idades são diferentes
2. hashCode()
Fornece um valor de código hash para os dados armazenados na classe, ajudando na eficiência de estruturas de dados como HashSet
e HashMap
.
fun adicionaPessoas() {
val conjuntoPessoas = hashSetOf<Pessoa>()
val pessoaA = Pessoa("Guilherme", 18)
val pessoaB = Pessoa("Guilherme", 18) // Mesmos dados que pessoaA
conjuntoPessoas.add(pessoaA)
conjuntoPessoas.add(pessoaB) // Tentativa de adicionar uma "duplicata"
println(conjuntoPessoas.size) // Imprime 1, pois pessoaA e pessoaB são consideradas iguais graças ao método equals() e têm o mesmo hashCode()
}
💡 Normalmente, não se lida diretamente com esse método, mas ele opera nos bastidores para garantir a correta operação de algumas coleções.
3. toString()
Converte os dados da classe para uma representação em string de forma legível e estruturada. Por padrão, ele exibe o nome da classe seguido por seus campos (nomes e valores) em ordem de declaração
val juliana = Pessoa("Juliana", 16)
println(juliana) // Imprime "Pessoa(nome=Juliana, idade=16)"
// println(juliana.toString())
💡No Kotlin, e também em muitas outras linguagens de programação, ao imprimir um objeto diretamente (como usando
println(juliana)
), o métodotoString()
daquele objeto é chamado implicitamente.
4. copy()
Cria uma cópia superficial do objeto. Pode-se também modificar alguns dos valores ao copiar.
val pessoaOriginal = Pessoa("Tiago", 33)
val pessoaModificada = pessoaOriginal.copy(idade = 34)
println(pessoaOriginal) // Imprime "Pessoa(nome=Tiago, idade=33)"
println(pessoaModificada) // Imprime "Pessoa(nome=Tiago, idade=34)"
5. componentN()
Essas funções fornecem uma maneira direta de acessar os atributos do objeto, onde N
é a ordem do atributo na declaração da classe.
val ana = Pessoa("Ana", 28)
println(ana.component1()) // Saída: Ana
println(ana.component2()) // Saída: 28
//println(ana.component3()) // Se existesse um 3 atributo de classe...
Desconstrução de objetos
A desconstrução é um recurso que permite decompor um objeto em várias variáveis. É uma prática super comum em outras linguagens, como JavaScript.
Isso é especialmente útil quando se quer trabalhar com
partes específicas de um objeto sem a necessidade de acessar individualmente os seus atributos.
Graças a função componentN()
, nós podemos usufruir dessa praticidade no Kotlin:
val jonas = Pessoa("Jonas", 42)
// Desconstruíndo...
val (nomeJonas, idadeJonas) = jonas
println(nomeJonas) // Saída: Jonas
println(idadeJonas) // Saída: 42
Desconstruíndo parâmetros de um lambda
A desconstrução também é útil quando trabalhamos com lambdas, especialmente quando se lida com pares ou trios:
val listaPessoas = listOf(Pessoa("Carlos", 32), Pessoa("Marta", 29))
listaPessoas.forEach { (nome, idade) ->
println("$nome tem $idade anos.")
}
Desconstruíndo objetos em classes "normais"
É possível desconstruir uma classe em Kotlin mesmo que ela não seja uma data class
.
Porém, para fazer isso, você precisa definir manualmente as funções componentN()
para cada propriedade que deseja desconstruir:
class Livro(val titulo: String, val autor: String) {
operator fun component1() = titulo
operator fun component2() = autor
}
val meuLivro = Livro("O Grande Livro", "João Silva")
val (tituloDoLivro, autorDoLivro) = meuLivro
println(tituloDoLivro) // Saída: O Grande Livro
println(autorDoLivro) // Saída: João Silva
ℹ️ Essa prática não é comum e raramente é necessária, porém possível
💡 Em Kotlin, a palavra-chave "operator" permite que funções se comportem como operadores tradicionais (como +, -, *, etc.) ou permita operações específicas (como a desconstrução de objetos).
Data Classes e a Imutabilidade
A imutabilidade se refere à impossibilidade de um objeto ter seu estado alterado após sua criação. Em outras palavras, uma vez que um objeto imutável é inicializado, seus dados não podem ser modificados.
No Kotlin, as data classes
são frequentemente usadas em conjunção com a imutabilidade. Ao usar val
em vez de var
, a propriedade torna-se somente de leitura, garantindo que, uma vez criado um objeto Pessoa
, seu nome e idade não possam ser alterados.
data class Pessoa(val nome: String, val idade: Int)
Vantagens da Imutabilidade com Data Classes
Menos erros em tempo de execução: como o estado de um objeto imutável não muda, a possibilidade de efeitos colaterais inesperados que podem levar a erros é reduzida.
Concorrência segura: objetos imutáveis são naturalmente seguros para serem usados em ambientes concorrentes, pois não há risco dealteração simultânea por múltiplas threads.
Expressividade:
data classes
imutáveis simplificam a lógica, pois é possível assumir que o estado do objeto permanecerá constante.Uso eficaz com coleções imutáveis: em Kotlin, existem coleções imutáveis (como
listOf
,setOf
), e o uso dedata classes
imutáveis com essas coleções torna o código ainda mais robusto.
Benefícios de desempenho
Otimização do compilador mais facilidade em realizar otimizações porque pode supor sobre o comportamento do código.
Garbage collection: como os objetos imutáveis não são modificados, eles podem ser reutilizados em vez de criados novamente, podendo reduzir a pressão sobre o garbage collector.
Previsibilidade e cache: como objetos imutáveis não mudam seu estado, eles são mais previsíveis. Isso pode permitir otimizações de cache mais eficientes tanto no nível do compilador quanto em tempo de execução.