Any & Generics: Entendendo a Diferença através do Role Playing Game
Imagina que você está jogando um RPG e você tem uma mochila mágica que pode carregar qualquer tipo de item: poções, espadas, escudos, ou até um dragão miniatura! No mundo do D&D, o RPG (Role Paying Game ou jogo de interpretação de papéis) mais famoso, tem uma exatamente assim: a bolsa devoradora (bag of holding no original). Podemos dizer que o seu equivalente no Kotlin é o Any. Ele é o tipo mais genérico que existe e pode carregar qualquer tipo de dado que você imaginar. Quer guardar um número? Beleza. Uma string? Tranquilo. Um objeto complexo? Sem problemas!
Principais características
O Any é o pai de todos os tipos em Kotlin, é a superclasse da qual todas as classes herdam (inclusive as criadas por você), o que significa que qualquer variável pode ser atribuída a ele. E é por isso que ele pode ser qualquer coisa que você precise, desde simples números inteiros até objetos complexos, como dito acima. Pense nele como a bolsa devoradora do RPG D&D, capaz de se armazenar qualquer coisa dentro de si.
Exemplo:
val qualquerCoisa: Any = "Eu sou um String!"
val outraCoisa: Any = 42
Além disso, o Any define três métodos:
- toString(): retorna a representação em string do objeto
- hashCode(): retorna um código hash para o objeto
- equals(): compara se outro objeto é igual a este
E como todas as classes herdam de Any, esses métodos estão disponíveis em todas elas.
Usos mais comuns
Sabe quando seu personagem no RPG encontra itens que não faz ideia para o que podem servir, mas decide guardá-los até descobrir o que fazer? Bom, nesse jogo também há limitação de peso e espaço, portanto, despreocupação com espaço e peso só é possível com uma bolsa devoradora! Voltando ao Kotlin, você vai usar Any quando não souber de antemão o tipo exato que estará lidando. Percebe a semelhança? Agora imagine, por exemplo, que está criando uma função que precisa ser super flexível e trabalhar com diferentes tipos de dados e você nunca sabe qual será o dado recebido. Parece uma boa situação de uso para o Any, não é mesmo?
Exemplo:
fun processarDado(dado: Any) {
println(dado)
}
Pontos positivos e negativos
Como falamos antes, nossa bolsa devoradora, ou Any, é extremamente flexível e capaz de armazenar qualquer coisa que jogarmos nela. Sendo assim, perfeita para lidar com situações que só podem ser resolvidas com uma flexibilidade fora de série!
Porém, essa bolsa mágica também tem uma desvantagem. No mundo mágico do D&D, todas as bolsas desse tipo compartilham uma espécie de dimensão paralela, que é habitada por uma criatura. Por se mover livremente nessa dimensão, essa criatura pode acabar trocando os itens da sua bolsa com a de outro aventureiro ou até sumir com os seus itens.
Em Kotlin pode acontecer algo parecido, justamente por perder a segurança do tipo. Como pode ser qualquer coisa, a linguagem não tem como saber qual o tipo de informação está guardado ali e podem ocorrer erros que só serão percebidos em tempo de execução. Isso abre uma brecha de segurança bem perigosa.
Agora, imagine uma criatura que se transforma em qualquer objeto, mas geralmente limitado a um material (madeira, tecido, pedra). No D&D essa criatura se chama Mímico, ele se camufla para devorar aventureiros desavisados. No Kotlin, isso é o que chamamos de Generics <T>. O <T> pode ser qualquer tipo que você definir, assim como o mímico pode ser qualquer coisa, de um baú de tesouros até uma porta ou um livro de feitiços.
Principais características
Veja, os mímicos podem ser qualquer coisa em uma masmorra, e somente depois de revelar sua verdadeira natureza ou serem descobertos é que todos o reconhecem como tal até o final da partida. O Generics <T> não é muito diferente, já que permite que criemos funções, classes e até interfaces que funcionam com qualquer tipo de dado. Até que sejam instanciadas, até que sejam chamadas, o parâmetro pode ser qualquer coisa. O tipo de dado só se torna definido quando essas estruturas forem chamadas ou instanciadas. E uma vez definido o tipo de dado, o parâmetro passa a ser desse tipo até o final de sua execução.
Exemplo:
class Caixa<T>(private val item: T) {
fun pegarItem(): T {
return item
}
}
val caixaDeInt = Caixa(123)
val caixaDeString = Caixa("Um item qualquer")
No exemplo acima, a Classe Caixa e a função pegarItem, na primeira instância serão de tipo inteiro até o final de sua execução, depois do retorno do item caixaDeInt. Isso também vale para o objeto caixaDeString, que será tipo string até o final.
Usos mais comuns
O mímico pode ser transformar em qualquer coisa, mas tem plena segurança do que é. Bem assim é o <T>. Ele é excelente não apenas por sua flexibilidade, mas por manter segurança enquanto é flexível! Sendo assim, o <T> é excelente para quem quer criar coleções ou classes que podem trabalhar com diferentes tipos de dados sem perder a segurança.
Um exemplo muito bom disso seria criar uma interface, com um parâmetro de tipo <T> para abstrair alguma função que precisa ser usada por várias camadas da aplicação, mas dando o mesmo tratamento para tipos diferentes de dados.
Pontos positivos e negativos
Como vimos, o <T> é excelente para quem busca ao mesmo templo flexibilidade e a segurança da tipagem, evitando erros em tempo de execução. Em termos de D&D, o Mímico pode ser qualquer coisa, mas ele é seguro de quem é e não vai se perder na oportunidade de atacar uma pessoa aventureira desavisada.
Por outro lado, sua desvantagem é que pode ser um pouco complexo de entender e implementar se você estiver iniciando seus estudos no Kotlin por agora. Em termos de RPG, se sua personagem é uma aventureira iniciante, pode ter dificuldades para distinguir o que é ou não um mímico. Mas não se intimide! Continue acumulando pontos de experiência você chega lá!
*Nome dado a um dia de jogo de RPG de mesa
Agora vamos recapitular essa sessão*?
Nós vimos que Any é incrivelmente flexível, podendo lidar com tudo o que jogarmos nele, mas não é muito seguro. Como a bolsa devoradora, que tem capacidade incrível de armazenamento, mas tem aquela questão do monstro que pode sumir com alguns dos equipamentos.
Também vimos que o Generics <T> é muito flexível, podendo se passar por qualquer tipo de dado, mas mantem a segurança de tipagem. Assim como o mímico pode ser qualquer coisa, mas nunca deixa de ser ele mesmo, é seguro de quem é.
Exemplo com Any e com <T>:
fun processarQualquerCoisa(dado: Any) {
println("Processando: $dado")
}
fun <T> processarComSeguranca(dado: T) {
println("Processando com segurança: $dado")
}
O que podemos tirar disso tudo é que:
Se você precisar de algo que possa lidar com qualquer coisa, possua flexibilidade máxima, mas cuja segurança não importe muito (ou não exista outra forma de lidar com a situação), Any é a sua escolha.
Agora, se você precisa de flexibilidade, mantendo segurança de tipos, como em coleções, classes e interfaces que irão lidar com diferentes tipos de dados, o <T> é a sua pedida! Inclusive, entre os dois, pessoalmente recomendo que use sempre este, a não ser em casos muito especiais em que não haja outra saída.
E aí, gostou das dicas? Quer conversar sobre isso ou sobre RPG de mesa? Me segue no LinkedIn e vamos bater um papo legal sobre desenvolvimento Android e Kotlin! 🚀
#Kotlin #AndroidDev #ProgrammingTips
Ilustrações de capas: gerada pela lexica.co e encontradas no google
Criação do Conteúdo: Escrito por autor humano, com uso do ChatGPT para ajuda e revisões