"Olá, amigo!"

"Oi, Ellie!"

"Hoje, Rishi e eu vamos contar tudo sobre genéricos."

"Espera, acho que já sei quase tudo."

"Quase tudo, mas não tudo."

"Sério? OK, estou pronto para ouvir."

"Então vamos começar."

"Em Java, os genéricos são classes que possuem parâmetros de tipo."

"Quanto ao motivo pelo qual os genéricos foram inventados, veja os comentários no código:"

Exemplo
ArrayList stringList = new ArrayList();
stringList.add("abc"); // Add a string to the list
stringList.add("abc"); // Add a string to the list
stringList.add( 1 ); // Add a number to the list

for(Object o: stringList)
{
 String s = (String) o; // There will be an exception here when we get to the integer
}

Como resolver o problema usando Genéricos:

Exemplo
ArrayList<String> stringList = new ArrayList<String>();
stringList.add("abc"); // Add a string to the list
stringList.add("abc"); // Add a string to the list
stringList.add( 1 ); // There will be a compilation error here

for(Object o: stringList)
{
 String s = (String) o;
}

"Este código simplesmente não será compilado e o erro causado pela adição do tipo de dados errado será notado durante a compilação."

"Sim, eu já sei disso."

"Ok, bom. A repetição é a mãe do aprendizado."

"Mas os criadores de Java foram um pouco preguiçosos quando criaram genéricos. Em vez de criar tipos completos com parâmetros, eles adotaram uma otimização engenhosa. Na realidade, eles não adicionaram nenhuma informação sobre parâmetros de tipo aos genéricos. Em vez disso, todos os mágica ocorre durante a compilação."

Código com genéricos
List<String> strings = new ArrayList<String>();
strings.add("abc");
strings.add("abc");
strings.add( 1); // Compilation error

for(String s: strings)
{
 System.out.println(s);
}
O que realmente acontece
List strings = new ArrayList();

strings.add((String)"abc");
strings.add((String)"abc");
strings.add((String) 1); // Compilation error

for(String s: strings)
{
 System.out.println(s);
}

"Isso é liso."

"Sim, mas essa abordagem tem um efeito colateral.  Nenhuma informação sobre os parâmetros de tipo é armazenada dentro de uma classe genérica.  Essa abordagem mais tarde ficou conhecida como eliminação de tipo ."

"Em outras palavras, se você tiver sua própria classe com parâmetros de tipo, não poderá usar informações sobre eles dentro da classe."

Código com genéricos
class Zoo<T>
{
 ArrayList<T> pets = new ArrayList<T>();

 public T createAnimal()
 {
  T animal = new T();
  pets.add(animal)
  return animal;
 }
}
O que realmente acontece
class Zoo
{
 ArrayList pets = new ArrayList();

 public Object createAnimal()
 {
  Object animal = new ???();
  pets.add(animal)
  return animal;
 }
}

"Durante a compilação, todos os tipos de parâmetros são substituídos por Object. E dentro da classe não há informações sobre o tipo passado para ela."

"Sim, eu concordo, isso não é o melhor."

"Não é tão assustador. Eu vou te dizer mais tarde como contornar esse problema."

Mas há mais. Java permite especificar um tipo pai para parâmetros de tipo. A palavra-chave extends é usada para isso. Por exemplo:

Código com genéricos
class Zoo<T extends Cat>
{
 T cat;

 T getCat()
 {
  return cat;
 }

 void setCat (T cat)
 {
  this.cat = cat;
 }

 String getCatName()
 {
  return this.cat.getName();
 }
}
O que realmente acontece
class Zoo
{
 Cat cat;

 Cat getCat()
 {
  return cat;
 }

 void setCat(Cat cat)
 {
  this.cat = cat;
 }

 String getCatName()
 {
  return this.cat.getName();
 }
}

"Observe dois fatos:"

"Primeiro, você não pode passar qualquer tipo como parâmetro - você só pode passar um Cat ou uma classe que herda Cat."

"Em segundo lugar, dentro da classe Zoo, as variáveis ​​do tipo T agora podem chamar os métodos da classe Cat.  A coluna à direita explica o porquê (porque Cat será substituído em todos os lugares onde houver um T)"

"Sim. Se dissermos que Cat ou uma subclasse de Cat é passada como argumento de tipo, temos certeza de que o tipo T sempre terá os métodos da classe Cat."

"Bem, isso é inteligente."