Genéricos significam tipos parametrizados . A ideia é permitir que o tipo (Integer, String, ... etc, e tipos definidos pelo usuário) seja um parâmetro para métodos, classes e interfaces. Usando Genéricos, é possível criar classes que trabalham com diferentes tipos de dados. 
Uma entidade como classe, interface ou método que opera em um tipo parametrizado é chamada de entidade genérica.

Por que genéricos?

O Object é a superclasse de todas as outras classes e a referência de Object pode referir-se a qualquer tipo de objeto. Esses recursos não têm segurança de tipo. Os genéricos adicionam esse tipo de recurso de segurança. Discutiremos esse tipo de recurso de segurança em exemplos posteriores.
Genéricos em Java são semelhantes aos modelos em C++ . Por exemplo, classes como HashSet, ArrayList, HashMap, etc usam genéricos muito bem. Existem algumas diferenças fundamentais entre as duas abordagens para tipos genéricos. 
 
Classe genérica 
Como C++, usamos <> para especificar os tipos de parâmetro na criação de classe genérica. Para criar objetos de uma classe genérica, usamos a seguinte sintaxe. 
 

// To create an instance of generic class 
BaseType <Type> obj = new BaseType <Type>()

Note: In Parameter type we can not use primitives like 
      'int','char' or 'double'.
// A Simple Java program to show working of user defined
// Generic classes
   
// We use < > to specify Parameter type
class Test<T>
{
    // An object of type T is declared
    T obj;
    Test(T obj) {  this.obj = obj;  }  // constructor
    public T getObject()  { return this.obj; }
}
   
// Driver class to test above
class Main
{
    public static void main (String[] args)
    {
        // instance of Integer type
        Test <Integer> iObj = new Test<Integer>(15);
        System.out.println(iObj.getObject());
   
        // instance of String type
        Test <String> sObj =
                          new Test<String>("GeeksForGeeks");
        System.out.println(sObj.getObject());
    }
}

Saída: 
 

15
GeeksForGeeks

Também podemos passar vários parâmetros de tipo em classes genéricas.
 

// A Simple Java program to show multiple
// type parameters in Java Generics
  
// We use < > to specify Parameter type
class Test<T, U>
{
    T obj1;  // An object of type T
    U obj2;  // An object of type U
  
    // constructor
    Test(T obj1, U obj2)
    {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }
  
    // To print objects of T and U
    public void print()
    {
        System.out.println(obj1);
        System.out.println(obj2);
    }
}
  
// Driver class to test above
class Main
{
    public static void main (String[] args)
    {
        Test <String, Integer> obj =
            new Test<String, Integer>("GfG", 15);
  
        obj.print();
    }
}

Saída: 
 

GfG
15

Funções genéricas: 
também podemos escrever funções genéricas que podem ser chamadas com diferentes tipos de argumentos com base no tipo de argumentos passados ​​para o método genérico, o compilador lida com cada método.
 

// A Simple Java program to show working of user defined
// Generic functions
   
class Test
{
    // A Generic method example
    static <T> void genericDisplay (T element)
    {
        System.out.println(element.getClass().getName() +
                           " = " + element);
    }
   
    // Driver method
    public static void main(String[] args)
    {
         // Calling generic method with Integer argument
        genericDisplay(11);
   
        // Calling generic method with String argument
        genericDisplay("GeeksForGeeks");
   
        // Calling generic method with double argument
        genericDisplay(1.0);
    }
}

Saída : 
 

java.lang.Integer = 11
java.lang.String = GeeksForGeeks
java.lang.Double = 1.0

Os genéricos funcionam apenas com tipos de referência: 
quando declaramos uma instância de um tipo genérico, o argumento de tipo passado para o parâmetro de tipo deve ser um tipo de referência. Não podemos usar tipos de dados primitivos como int , char.
 

Test<int> obj = new Test<int>(20); 

A linha acima resulta em um erro de tempo de compilação, que pode ser resolvido usando wrappers de tipo para encapsular um tipo primitivo. 

Mas a matriz de tipo primitivo pode ser passada para o parâmetro de tipo porque as matrizes são do tipo de referência.

ArrayList<int[]> a = new ArrayList<>();

Tipos genéricos diferem com base em seus argumentos de tipo: 

Considere o seguinte código Java. 

// A Simple Java program to show working
// of user-defined Generic classes
   
// We use < > to specify Parameter type
class Test<T>
{
    // An object of type T is declared
    T obj;
    Test(T obj) {  this.obj = obj;  }  // constructor
    public T getObject()  { return this.obj; }
}
   
// Driver class to test above
class Main
{
    public static void main (String[] args)
    {
        // instance of Integer type
        Test <Integer> iObj = new Test<Integer>(15);  
        System.out.println(iObj.getObject());
   
        // instance of String type
        Test <String> sObj =
                          new Test<String>("GeeksForGeeks");
        System.out.println(sObj.getObject());
        iObj = sObj; //This results an error  
    }
}

Saída: 
 

 
error:
 incompatible types:
 Test cannot be converted to Test 

Mesmo que iObj e sObj sejam do tipo Test, eles são as referências para tipos diferentes porque seus parâmetros de tipo diferem. Os genéricos adicionam segurança de tipo por meio disso e evitam erros.
Vantagens dos genéricos: 
Programas que usam genéricos têm muitos benefícios em relação ao código não genérico. 
 

1. Reutilização de código: Podemos escrever um método / classe / interface uma vez e usá-lo para qualquer tipo que desejarmos.

2. Segurança de tipo: os genéricos fazem com que os erros apareçam em tempo de compilação do que em tempo de execução (é sempre melhor saber os problemas em seu código em tempo de compilação em vez de fazer com que seu código falhe em tempo de execução). Suponha que você queira criar um ArrayList que armazene o nome dos alunos e, se por engano o programador adicionar um objeto inteiro em vez de uma string, o compilador permitirá. Mas, quando recuperamos esses dados de ArrayList, isso causa problemas no tempo de execução.

// A Simple Java program to demonstrate that NOT using
// generics can cause run time exceptions
import java.util.*;
  
class Test
{
    public static void main(String[] args)
    {
        // Creatinga an ArrayList without any type specified
        ArrayList al = new ArrayList();
  
        al.add("Sachin");
        al.add("Rahul");
        al.add(10); // Compiler allows this
  
        String s1 = (String)al.get(0);
        String s2 = (String)al.get(1);
  
        // Causes Runtime Exception
        String s3 = (String)al.get(2);
    }
}

Saída :

Exception in thread "main" java.lang.ClassCastException: 
   java.lang.Integer cannot be cast to java.lang.String
    at Test.main(Test.java:19)

Como os genéricos resolvem esse problema?  
No momento de definir ArrayList, podemos especificar que essa lista pode conter apenas objetos String.
 

// Using generics converts run time exceptions into 
// compile time exception.
import java.util.*;
  
class Test
{
    public static void main(String[] args)
    {
        // Creating a an ArrayList with String specified
        ArrayList <String> al = new ArrayList<String> ();
  
        al.add("Sachin");
        al.add("Rahul");
  
        // Now Compiler doesn't allow this
        al.add(10); 
  
        String s1 = (String)al.get(0);
        String s2 = (String)al.get(1);
        String s3 = (String)al.get(2);
    }
}
Saída:
15: error: no suitable method found for add(int)
        al.add(10); 
          ^

3. Fundição de tipo individual não é necessária : Se não usarmos genéricos, então, no exemplo acima, toda vez que recuperamos dados de ArrayList, temos que fazer a projeção de tipos . A projeção de tipos em cada operação de recuperação é uma grande dor de cabeça. Se já sabemos que nossa lista contém apenas dados de string, não precisamos fazer o typecast todas as vezes.

// We don't need to typecast individual members of ArrayList
import java.util.*;
  
class Test
{
    public static void main(String[] args)
    {
        // Creating a an ArrayList with String specified
        ArrayList <String> al = new ArrayList<String> ();
  
        al.add("Sachin");
        al.add("Rahul");
  
        // Typecasting is not needed 
        String s1 = al.get(0);
        String s2 = al.get(1);
    }
}

4. Os genéricos promovem a reutilização do código.
5. Implementação de algoritmos genéricos: Usando genéricos, podemos implementar algoritmos que funcionam em diferentes tipos de objetos e, ao mesmo tempo, são seguros para o tipo.

Referências:  
https://docs.oracle.com/javase/tutorial/java/generics/why.html
Este artigo foi contribuído por Dharmesh Singh . Se você gosta de GeeksforGeeks e gostaria de contribuir, você também pode escrever um artigo e enviá-lo para contrib@geeksforgeeks.org. Veja o seu artigo na página principal do GeeksforGeeks e ajude outros Geeks.
Escreva comentários se encontrar algo incorreto ou se quiser compartilhar mais informações sobre o tópico discutido acima