Conceitos de Genéricos em Java

JavaBeginner
Pratique Agora

Introdução

Genéricos em Java permitem escrever código flexível e reutilizável, fornecendo uma camada adicional de abstração. Eles permitem criar um único algoritmo para múltiplos tipos de objetos. Genéricos fornecem segurança de tipo e evitam erros em tempo de execução. Neste laboratório, entenderemos vários aspectos dos Genéricos em Java.

Criando uma Classe Genérica

Podemos criar uma classe Genérica em Java com a ajuda do operador diamante (<>). No bloco de código a seguir, criaremos uma classe Genérica chamada MyGenericClass que pode aceitar qualquer tipo de objeto. Também criaremos um objeto do tipo MyGenericClass com tipos inteiro e string como parâmetros.

// ~/project/MyGenericClass.java

class MyGenericClass<T> {
    T field1;

    public MyGenericClass(T field1) {
        this.field1 = field1;
    }

    public T getField1() {
        return field1;
    }
}

public class Main {
    public static void main(String[] args) {
        MyGenericClass<Integer> myIntObj = new MyGenericClass<Integer>(100);
        MyGenericClass<String> myStringObj = new MyGenericClass<String>("Hello World");

        System.out.println(myIntObj.getField1());
        System.out.println(myStringObj.getField1());
    }
}

Para executar o código, abra o terminal e navegue até o diretório ~/project. Compile o código com o seguinte comando:

javac MyGenericClass.java

Após a compilação bem-sucedida, execute o código com o seguinte comando:

java Main

A saída do código será:

100
Hello World

Criando um Método Genérico

Também podemos criar um método Genérico em Java. O método Genérico pode trabalhar com diferentes tipos de objetos. No bloco de código a seguir, criaremos um método Genérico chamado printArray que pode imprimir qualquer tipo de array.

// ~/project/Main.java

public class Main {
    public static <T> void printArray(T[] array) {
        for (T element : array)
            System.out.println(element);
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
        String[] stringArray = {"Hello", "World"};

        printArray(intArray);
        printArray(doubleArray);
        printArray(stringArray);
    }
}

Para executar o código, abra o terminal e navegue até o diretório ~/project. Compile o código com o seguinte comando:

javac Main.java

Após a compilação bem-sucedida, execute o código com o seguinte comando:

java Main

A saída do código será:

1
2
3
4
5
1.1
2.2
3.3
4.4
Hello
World

Criando um Método Genérico com Limites (Bounded)

Também podemos criar um método Genérico Limitado. Genéricos Limitados restringem a gama de tipos aceitos pelo método. Usamos a palavra-chave extends para impor o limite (bound). No bloco de código a seguir, criaremos um método Genérico Limitado chamado printNumbers que pode imprimir os números de objetos do tipo Number e suas subclasses.

// ~/project/Main.java

public class Main {
    public static <T extends Number> void printNumbers(T[] numbers) {
        for (T number : numbers)
            System.out.println(number);
    }

    public static void main(String[] args) {
        Integer[] intNumbers = {1, 2, 3, 4, 5};
        Double[] doubleNumbers = {1.1, 2.2, 3.3, 4.4};

        printNumbers(intNumbers);
        printNumbers(doubleNumbers);
    }
}

Para executar o código, abra o terminal e navegue até o diretório ~/project. Compile o código com o seguinte comando:

javac Main.java

Após a compilação bem-sucedida, execute o código com o seguinte comando:

java Main

A saída do código será:

1
2
3
4
5
1.1
2.2
3.3
4.4

Entendendo a Segurança de Tipos em Generics

Genéricos fornecem segurança de tipos, garantindo que definimos apenas um tipo de objeto para trabalhar por vez. No bloco de código a seguir, criaremos um ArrayList de inteiros sem definir seu parâmetro de tipo. É uma má prática usar Genéricos sem parâmetros de tipo. O código compilará, mas retornará um erro em tempo de execução quando tentarmos acessar seus elementos de outros tipos.

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList integerList = new ArrayList();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        integerList.add("Hello World");

        for (Object element : integerList) {
            System.out.println((Integer)element);
        }
    }
}

Para executar o código, abra o terminal e navegue até o diretório ~/project. Compile o código com o seguinte comando:

javac Main.java

Após a compilação bem-sucedida, execute o código com o seguinte comando:

java Main

A saída do código será:

1
2
3
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.lang.Integer
    at Main.main(Main.java:13)

Obtemos um erro em tempo de execução porque o integerList contém elementos do tipo Integer e do tipo String. Isso ocorre porque não definimos o parâmetro de tipo ao criar o integerList.

Entendendo a Type Erasure em Generics

Java usa a Eliminação de Tipos (Type Erasure) em Genéricos para garantir que nenhuma sobrecarga adicional seja necessária em tempo de execução. O compilador substitui o parâmetro de tipo pela classe Object ou pela classe pai (no caso de Genéricos limitados) durante o processo de eliminação de tipos. No bloco de código a seguir, veremos como os Genéricos funcionam com tipos primitivos em tempo de compilação e tempo de execução.

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);

        int sum = 0;
        for (int i = 0; i < integerList.size(); i++) {
            sum += integerList.get(i);
        }
        System.out.println(sum);
    }
}

Para executar o código, abra o terminal e navegue até o diretório ~/project. Compile o código com o seguinte comando:

javac Main.java

Após a compilação bem-sucedida, execute o código com o seguinte comando:

java Main

A saída do código será:

6

No bloco de código acima, tentamos adicionar os elementos do integerList. Como sabemos, tipos primitivos como int não podem ser usados com Genéricos. Devido à eliminação de tipos, o parâmetro de tipo Integer é substituído pela classe Object em tempo de compilação. No entanto, autoboxing e unboxing nos permitiram realizar a adição de inteiros em tempo de compilação. Em tempo de execução, o código foi executado como uma execução de código normal, sem qualquer sobrecarga adicional.

Resumo

Neste laboratório, aprendemos sobre Genéricos em Java. Cobrimos os conceitos básicos relacionados a classes genéricas, métodos genéricos, métodos genéricos limitados, segurança de tipos em genéricos e eliminação de tipos (Type Erasure) em genéricos. Também vimos como escrever código flexível e reutilizável usando Genéricos. Esperamos que este laboratório tenha ajudado você a entender Genéricos em Java.