Fala, pessoal. Tudo bem? Vamos falar um pouco sobre generics no post de hoje.
Generics apareceu na versão 5 de Java, e trouxe funcionalidades interessantes para o reuso de código. Agora, podemos criar uma classe só e, a partir dessa classe, instanciar objetos de diferentes tipos, de acordo com a nossa escolha. Vamos entender o problema que gerou a criação do generics primeiro, e depois partir pra ele.
Antes do generics
Vamos criar uma classe que recebe um número inteiro e possui um método que retorna o quadrado do número.
public class MinhaClasse {
public int num;
public MinhaClasse(int num) {
this.num = num;
}
public int aoQuadrado() {
return num * num;
}
}
// ----- Classe teste abaixo
public class MinhaClasseTeste {
public static void main(String[] args) throws Exception {
MinhaClasse inteira = new MinhaClasse(2);
System.out.println(inteira.aoQuadrado());
}
}
// output: 4
Tudo certo. No entanto, agora quero usar valores do tipo Double também. Poderíamos fazer assim:
public class MinhaClasse{
public int num;
public Double num1;
public MinhaClasse(int num) {
this.num = num;
}
public MinhaClasse(Double num1) {
this.num1 = num1;
}
public int aoQuadradoInt() {
return num * num;
}
public Double aoQuadradoDouble() {
return num1 * num1;
}
}
Agora, se o tipo recebido no construtor for Double, o objeto será do tipo Double. Depois é só invocar com obj.aoQuadradoDouble(). Poderíamos também criar outra classe idêntica, só que para trabalhar com Double.
Mas perceba que o código na classe fica bagunçado e repetido. E o generics resolve muito bem esse problema. Então, vamos alterar a classe MinhaClasse para trabalhar com essa feature.
Generics
Antes, uma demonstração somente com um método que printa o valor do objeto:
public class MinhaClasse<T> {
T obj;
public MinhaClasse(T obj) {
this.obj = obj;
}
public void printar() {
System.out.println(obj);
}
}
// ----- Classe para testar
public class MinhaClasseTeste {
public static void main(String[] args) throws Exception {
MinhaClasse<Integer> inteiro = new MinhaClasse<>(2);
inteiro.printar();
MinhaClasse<Double> decimal = new MinhaClasse<>(2.0);
decimal.printar();
MinhaClasse<String> palavra = new MinhaClasse<>("Sou uma String");
palavra.printar();
}
}
/* output:
2
2.0
Sou uma String
Para usar generics, utilizamos o diamante(<>) com um tipo genérico; aqui, utilizamos o T que é uma convenção, mas funcionaria com X por exemplo. Depois criamos um atributo obj do tipo T, criamos o construtor e um método que retorna o valor desse obj.
Agora, para instanciar o objeto, precisamos passar entre <> o tipo que vamos passar para o construtor; fizemos isso com Integer, Double e String(tipos primitivos não funcionam).
De agora em diante, decidimos em uma só classe criada o tipo do objeto que estamos criando.
Voltando para o método do quadrado
Ao trabalhar com valores numéricos, precisamos fazer uma alteração para tudo funcionar corretamente. Se fizermos da forma a seguir, um erro será gerado:
O próprio Java vai nos prevenir de usar a multiplicação pro tipo genérico T; afinal, podemos passar um String sem querer. Para corrigir isso e sinalizar que vamos utilizar apenas números, é só fazer a seguinte alteração:
public class MinhaClasse<T extends Number> { // T agora dá um extends na classe Number
T numClasse;
public MinhaClasse(T numClasse) {
this.numClasse = numClasse;
}
// O erro que aparecia aqui agora some
Double aoQuadrado() {
return numClasse.intValue() * numClasse.doubleValue();
}
// Outro exemplo agora usando <?> que vou explicar abaixo
boolean saoIguais(MinhaClasse<?> obj) {
if(Math.abs(numClasse.doubleValue()) == Math
.abs(obj.numClasse.doubleValue())) {
return true;
}
return false;
}
}
// ----- classe teste
public class MinhaClasseTeste {
public static void main(String[] args) throws Exception {
MinhaClasse<Integer> inteiro = new MinhaClasse<>(5);
MinhaClasse<Double> doubleTipo = new MinhaClasse<>(5.0);
System.out.println(inteiro.aoQuadrado() + " | " + doubleTipo.aoQuadrado());
System.out.println(inteiro.saoIguais(doubleTipo));
}
}
/* output:
25.0 | 25.0
true
*/
Eu coloquei um método a mais, chamado “saoIguais”, que checa se os dois valores absolutos são iguais. Se adicionarmos o T e não o ?, um erro será gerado, pois o Java esperará um inteiro, que é o valor do inteiro.saoIguais(), e se fizermos doubleTipo.saoIguais(), ele esperará um Double. Usando ?, deixamos claro pro Java que ele pode esperar tanto um inteiro quanto um double, ou seja: podemos misturar os tipos.
Conclusão
Enfim, é importante que você copie esses códigos, modifique e pratique na sua máquina, para observar melhor todos os comportamentos. O nome generics vem do fato de você ter um tipo genérico na sua classe. Usando generics porém, você deve especificar o tipo que será usado quando você estiver instanciando um objeto dessa classe.
É um tanto confuso, por isso a prática é fundamental; fica o desafio de criar uma classe genérica e alguns métodos da sua escolha. Até logo!