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!