Java Generics - Guia rápido
Seria bom se pudéssemos escrever um único método de classificação que pudesse classificar os elementos em um array Integer, um array String ou um array de qualquer tipo que suporte ordenação.
Métodos Java genéricos e classes genéricas permitem que os programadores especifiquem, com uma única declaração de método, um conjunto de métodos relacionados, ou com uma única declaração de classe, um conjunto de tipos relacionados, respectivamente.
Os genéricos também fornecem segurança de tipo em tempo de compilação que permite aos programadores detectar tipos inválidos em tempo de compilação.
Usando o conceito Java Genérico, podemos escrever um método genérico para classificar uma matriz de objetos e, em seguida, invocar o método genérico com matrizes Integer, matrizes Double, matrizes String e assim por diante, para classificar os elementos da matriz.
Configuração de ambiente local
JUnit é uma estrutura para Java, portanto, o primeiro requisito é ter o JDK instalado em sua máquina.
Requisitos do sistema
JDK | 1,5 ou superior. |
---|---|
Memória | Sem requisitos mínimos. |
Espaço em disco | Sem requisitos mínimos. |
Sistema operacional | Sem requisitos mínimos. |
Etapa 1: verifique a instalação do Java em sua máquina
Em primeiro lugar, abra o console e execute um comando java baseado no sistema operacional em que está trabalhando.
SO | Tarefa | Comando |
---|---|---|
janelas | Abrir console de comando | c: \> versão java |
Linux | Abra o terminal de comando | $ java -version |
Mac | Terminal aberto | máquina: <joseph $ java -version |
Vamos verificar a saída de todos os sistemas operacionais -
SO | Resultado |
---|---|
janelas | versão java "1.6.0_21" Java (TM) SE Runtime Environment (build 1.6.0_21-b07) Cliente VM Java HotSpot (TM) (versão 17.0-b17, modo misto, compartilhamento) |
Linux | versão java "1.6.0_21" Java (TM) SE Runtime Environment (build 1.6.0_21-b07) Cliente VM Java HotSpot (TM) (versão 17.0-b17, modo misto, compartilhamento) |
Mac | versão java "1.6.0_21" Java (TM) SE Runtime Environment (build 1.6.0_21-b07) Servidor VM Java HotSpot (TM) de 64 bits (versão 17.0-b17, modo misto, compartilhamento) |
Se você não tiver o Java instalado em seu sistema, baixe o Java Software Development Kit (SDK) do seguinte link https://www.oracle.com. Estamos assumindo o Java 1.6.0_21 como a versão instalada para este tutorial.
Etapa 2: definir o ambiente JAVA
Colocou o JAVA_HOMEvariável de ambiente para apontar para o local do diretório base onde o Java está instalado em sua máquina. Por exemplo.
SO | Resultado |
---|---|
janelas | Defina a variável de ambiente JAVA_HOME como C: \ Arquivos de programas \ Java \ jdk1.6.0_21 |
Linux | export JAVA_HOME = / usr / local / java-current |
Mac | export JAVA_HOME = / Library / Java / Home |
Anexe a localização do compilador Java ao caminho do sistema.
SO | Resultado |
---|---|
janelas | Anexar a string C:\Program Files\Java\jdk1.6.0_21\bin no final da variável do sistema, Path. |
Linux | exportar PATH = $PATH:$JAVA_HOME / bin / |
Mac | não requerido |
Verifique a instalação do Java usando o comando java -version conforme explicado acima.
Uma declaração de classe genérica parece uma declaração de classe não genérica, exceto que o nome da classe é seguido por uma seção de parâmetro de tipo.
A seção de parâmetro de tipo de uma classe genérica pode ter um ou mais parâmetros de tipo separados por vírgulas. Essas classes são conhecidas como classes parametrizadas ou tipos parametrizados porque aceitam um ou mais parâmetros.
Sintaxe
public class Box<T> {
private T t;
}
Onde
Box - Box é uma classe genérica.
T- O parâmetro de tipo genérico passado para a classe genérica. Pode levar qualquer objeto.
t - Instância do tipo genérico T.
Descrição
OT é um parâmetro de tipo passado para a classe genérica Box e deve ser passado quando um objeto Box é criado.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Isso produzirá o seguinte resultado.
Resultado
Integer Value :10
String Value :Hello World
Por convenção, os nomes dos parâmetros de tipo são nomeados como simples letras maiúsculas para que um parâmetro de tipo possa ser distinguido facilmente com uma classe comum ou nome de interface. A seguir está a lista de nomes de parâmetros de tipo comumente usados -
E - Elemento, e é usado principalmente pelo framework Java Collections.
K - Chave, e é usado principalmente para representar o tipo de parâmetro da chave de um mapa.
V - Valor, e é usado principalmente para representar o tipo de parâmetro de valor de um mapa.
N - Número, e é usado principalmente para representar números.
T - Digite e é usado principalmente para representar o primeiro parâmetro de tipo genérico.
S - Digite e é usado principalmente para representar o segundo parâmetro de tipo genérico.
U - Digite e é usado principalmente para representar o terceiro parâmetro de tipo genérico.
V - Digite e é usado principalmente para representar o quarto parâmetro de tipo genérico.
O exemplo a seguir mostrará o conceito mencionado acima.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
GenericsTester.java
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, String> box = new Box<Integer, String>();
box.add(Integer.valueOf(10),"Hello World");
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
Pair<String, Integer> pair = new Pair<String, Integer>();
pair.addKeyValue("1", Integer.valueOf(10));
System.out.printf("(Pair)Integer Value :%d\n", pair.getValue("1"));
CustomList<Box> list = new CustomList<Box>();
list.addItem(box);
System.out.printf("(CustomList)Integer Value :%d\n", list.getItem(0).getFirst());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
class Pair<K,V>{
private Map<K,V> map = new HashMap<K,V>();
public void addKeyValue(K key, V value) {
map.put(key, value);
}
public V getValue(K key) {
return map.get(key);
}
}
class CustomList<E>{
private List<E> list = new ArrayList<E>();
public void addItem(E value) {
list.add(value);
}
public E getItem(int index) {
return list.get(index);
}
}
Isso produzirá o seguinte resultado.
Resultado
Integer Value :10
String Value :Hello World
(Pair)Integer Value :10
(CustomList)Integer Value :10
A inferência de tipo representa a capacidade do compilador Java de examinar uma chamada de método e sua declaração correspondente para verificar e determinar o (s) argumento (s) de tipo. O algoritmo de inferência verifica os tipos de argumentos e, se disponível, o tipo atribuído é retornado. Os algoritmos de inferência tentam encontrar um tipo específico que pode preencher todos os parâmetros de tipo.
O compilador gera um aviso de conversão não verificado caso a inferência de tipo não seja usada.
Sintaxe
Box<Integer> integerBox = new Box<>();
Onde
Box - Box é uma classe genérica.
<> - O operador diamante denota inferência de tipo.
Descrição
Usando o operador diamante, o compilador determina o tipo do parâmetro. Este operador está disponível a partir do Java SE 7 versão em diante.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
//type inference
Box<Integer> integerBox = new Box<>();
//unchecked conversion warning
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Isso produzirá o seguinte resultado.
Resultado
Integer Value :10
String Value :Hello World
Você pode escrever uma única declaração de método genérico que pode ser chamada com argumentos de diferentes tipos. Com base nos tipos de argumentos passados para o método genérico, o compilador trata cada chamada de método apropriadamente. A seguir estão as regras para definir métodos genéricos -
Todas as declarações de método genérico têm uma seção de parâmetro de tipo delimitada por colchetes angulares (<e>) que precede o tipo de retorno do método (<E> no próximo exemplo).
Cada seção de parâmetro de tipo contém um ou mais parâmetros de tipo separados por vírgulas. Um parâmetro de tipo, também conhecido como variável de tipo, é um identificador que especifica um nome de tipo genérico.
Os parâmetros de tipo podem ser usados para declarar o tipo de retorno e atuar como marcadores para os tipos de argumentos passados para o método genérico, que são conhecidos como argumentos de tipo reais.
O corpo de um método genérico é declarado como o de qualquer outro método. Observe que os parâmetros de tipo podem representar apenas tipos de referência, não tipos primitivos (como int, double e char).
Exemplo
O exemplo a seguir ilustra como podemos imprimir uma matriz de tipo diferente usando um único método genérico -
public class GenericMethodTest {
// generic method printArray
public static < E > void printArray( E[] inputArray ) {
// Display array elements
for(E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
public static void main(String args[]) {
// Create arrays of Integer, Double and Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println("Array integerArray contains:");
printArray(intArray); // pass an Integer array
System.out.println("\nArray doubleArray contains:");
printArray(doubleArray); // pass a Double array
System.out.println("\nArray characterArray contains:");
printArray(charArray); // pass a Character array
}
}
Isso produzirá o seguinte resultado -
Resultado
Array integerArray contains:
1 2 3 4 5
Array doubleArray contains:
1.1 2.2 3.3 4.4
Array characterArray contains:
H E L L O
Uma classe genérica pode ter vários parâmetros de tipo. O exemplo a seguir mostrará o conceito mencionado acima.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, String> box = new Box<Integer, String>();
box.add(Integer.valueOf(10),"Hello World");
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
Box<String, String> box1 = new Box<String, String>();
box1.add("Message","Hello World");
System.out.printf("String Value :%s\n", box1.getFirst());
System.out.printf("String Value :%s\n", box1.getSecond());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
Isso produzirá o seguinte resultado.
Resultado
Integer Value :10
String Value :Hello World
String Value :Message
String Value :Hello World
Uma classe genérica pode ter tipos parametrizados em que um parâmetro de tipo pode ser substituído por um tipo parametrizado. O exemplo a seguir mostrará o conceito mencionado acima.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
GenericsTester.java
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, List<String>> box
= new Box<Integer, List<String>>();
List<String> messages = new ArrayList<String>();
messages.add("Hi");
messages.add("Hello");
messages.add("Bye");
box.add(Integer.valueOf(10),messages);
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
Isso produzirá o seguinte resultado.
Resultado
Integer Value :10
String Value :[Hi, Hello, Bye]
Um tipo bruto é um objeto de uma classe ou interface genérica se seus argumentos de tipo não forem passados durante sua criação. O exemplo a seguir mostrará o conceito mencionado acima.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
GenericsTester.java
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> box = new Box<Integer>();
box.set(Integer.valueOf(10));
System.out.printf("Integer Value :%d\n", box.getData());
Box rawBox = new Box();
//No warning
rawBox = box;
System.out.printf("Integer Value :%d\n", rawBox.getData());
//Warning for unchecked invocation to set(T)
rawBox.set(Integer.valueOf(10));
System.out.printf("Integer Value :%d\n", rawBox.getData());
//Warning for unchecked conversion
box = rawBox;
System.out.printf("Integer Value :%d\n", box.getData());
}
}
class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T getData() {
return t;
}
}
Isso produzirá o seguinte resultado.
Resultado
Integer Value :10
Integer Value :10
Integer Value :10
Integer Value :10
Pode haver momentos em que você queira restringir os tipos de tipos que podem ser passados para um parâmetro de tipo. Por exemplo, um método que opera em números pode aceitar apenas instâncias de Number ou suas subclasses. É para isso que servem os parâmetros de tipo limitado.
Para declarar um parâmetro de tipo limitado, liste o nome do parâmetro de tipo, seguido pela palavra-chave extends, seguida por seu limite superior.
Exemplo
O exemplo a seguir ilustra como extends é usado em um sentido geral para significar "estende" (como nas classes) ou "implementa" (como nas interfaces). Este exemplo é o método genérico para retornar o maior dos três objetos comparáveis -
public class MaximumTest {
// determines the largest of three Comparable objects
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // assume x is initially the largest
if(y.compareTo(max) > 0) {
max = y; // y is the largest so far
}
if(z.compareTo(max) > 0) {
max = z; // z is the largest now
}
return max; // returns the largest object
}
public static void main(String args[]) {
System.out.printf("Max of %d, %d and %d is %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ));
System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));
System.out.printf("Max of %s, %s and %s is %s\n","pear",
"apple", "orange", maximum("pear", "apple", "orange"));
}
}
Isso produzirá o seguinte resultado -
Resultado
Max of 3, 4 and 5 is 5
Max of 6.6,8.8 and 7.7 is 8.8
Max of pear, apple and orange is pear
Um parâmetro de tipo pode ter vários limites.
Sintaxe
public static <T extends Number & Comparable<T>> T maximum(T x, T y, T z)
Onde
maximum - o máximo é um método genérico.
T- O parâmetro de tipo genérico passado para o método genérico. Pode levar qualquer objeto.
Descrição
OT é um parâmetro de tipo passado para a classe genérica Box e deve ser um subtipo da classe Number e deve implementar a interface Comparable. No caso de uma classe ser passada como vinculada, ela deve ser passada primeiro antes da interface, caso contrário, ocorrerá um erro de tempo de compilação.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
System.out.printf("Max of %d, %d and %d is %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ));
System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));
}
public static <T extends Number
& Comparable<T>> T maximum(T x, T y, T z) {
T max = x;
if(y.compareTo(max) > 0) {
max = y;
}
if(z.compareTo(max) > 0) {
max = z;
}
return max;
}
// Compiler throws error in case of below declaration
/* public static <T extends Comparable<T>
& Number> T maximum1(T x, T y, T z) {
T max = x;
if(y.compareTo(max) > 0) {
max = y;
}
if(z.compareTo(max) > 0) {
max = z;
}
return max;
}*/
}
Isso produzirá o seguinte resultado -
Resultado
Max of 3, 4 and 5 is 5
Max of 6.6,8.8 and 7.7 is 8.8
Java forneceu suporte genérico na interface List.
Sintaxe
List<T> list = new ArrayList<T>();
Onde
list - objeto da interface List.
T - O parâmetro de tipo genérico passado durante a declaração da lista.
Descrição
OT é um parâmetro de tipo passado para a interface genérica List e sua classe de implementação ArrayList.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericsTester {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<Integer>();
integerList.add(Integer.valueOf(10));
integerList.add(Integer.valueOf(11));
List<String> stringList = new ArrayList<String>();
stringList.add("Hello World");
stringList.add("Hi World");
System.out.printf("Integer Value :%d\n", integerList.get(0));
System.out.printf("String Value :%s\n", stringList.get(0));
for(Integer data: integerList) {
System.out.printf("Integer Value :%d\n", data);
}
Iterator<String> stringIterator = stringList.iterator();
while(stringIterator.hasNext()) {
System.out.printf("String Value :%s\n", stringIterator.next());
}
}
}
Isso produzirá o seguinte resultado -
Resultado
Integer Value :10
String Value :Hello World
Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World
Java forneceu suporte genérico na interface Set.
Sintaxe
Set<T> set = new HashSet<T>();
Onde
set - objeto de Set Interface.
T - O parâmetro de tipo genérico passado durante a declaração de conjunto.
Descrição
OT é um parâmetro de tipo passado para a interface genérica Set e sua classe de implementação HashSet.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
package com.tutorialspoint;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class GenericsTester {
public static void main(String[] args) {
Set<Integer> integerSet = new HashSet<Integer>();
integerSet.add(Integer.valueOf(10));
integerSet.add(Integer.valueOf(11));
Set<String> stringSet = new HashSet<String>();
stringSet.add("Hello World");
stringSet.add("Hi World");
for(Integer data: integerSet) {
System.out.printf("Integer Value :%d\n", data);
}
Iterator<String> stringIterator = stringSet.iterator();
while(stringIterator.hasNext()) {
System.out.printf("String Value :%s\n", stringIterator.next());
}
}
}
Isso produzirá o seguinte resultado -
Resultado
Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World
Java forneceu suporte genérico na interface do mapa.
Sintaxe
Set<T> set = new HashSet<T>();
Onde
set - objeto de Set Interface.
T - O parâmetro de tipo genérico passado durante a declaração de conjunto.
Descrição
OT é um parâmetro de tipo passado para a interface genérica Set e sua classe de implementação HashSet.
Exemplo
Crie o seguinte programa java usando qualquer editor de sua escolha.
package com.tutorialspoint;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class GenericsTester {
public static void main(String[] args) {
Map<Integer,Integer> integerMap
= new HashMap<Integer,Integer>();
integerMap.put(1, 10);
integerMap.put(2, 11);
Map<String,String> stringMap = new HashMap<String,String>();
stringMap.put("1", "Hello World");
stringMap.put("2","Hi World");
System.out.printf("Integer Value :%d\n", integerMap.get(1));
System.out.printf("String Value :%s\n", stringMap.get("1"));
// iterate keys.
Iterator<Integer> integerIterator = integerMap.keySet().iterator();
while(integerIterator.hasNext()) {
System.out.printf("Integer Value :%d\n", integerIterator.next());
}
// iterate values.
Iterator<String> stringIterator = stringMap.values().iterator();
while(stringIterator.hasNext()) {
System.out.printf("String Value :%s\n", stringIterator.next());
}
}
}
Isso produzirá o seguinte resultado -
Resultado
Integer Value :10
String Value :Hello World
Integer Value :1
Integer Value :2
String Value :Hello World
String Value :Hi World
O ponto de interrogação (?), Representa o curinga, significa tipo desconhecido em genéricos. Pode haver momentos em que você queira restringir os tipos de tipos que podem ser passados para um parâmetro de tipo. Por exemplo, um método que opera em números pode aceitar apenas instâncias de Number ou suas subclasses.
Para declarar um parâmetro Wildcard de limite superior, liste o?, Seguido pela palavra-chave extends, seguida por seu limite superior.
Exemplo
O exemplo a seguir ilustra como extends é usado para especificar um curinga de limite superior.
package com.tutorialspoint;
import java.util.Arrays;
import java.util.List;
public class GenericsTester {
public static double sum(List<? extends Number> numberlist) {
double sum = 0.0;
for (Number n : numberlist) sum += n.doubleValue();
return sum;
}
public static void main(String args[]) {
List<Integer> integerList = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sum(integerList));
List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sum(doubleList));
}
}
Isso produzirá o seguinte resultado -
Resultado
sum = 6.0
sum = 7.0
O ponto de interrogação (?), Representa o curinga, significa tipo desconhecido em genéricos. Pode haver momentos em que qualquer objeto pode ser usado quando um método pode ser implementado usando a funcionalidade fornecida na classe Object ou quando o código é independente do parâmetro de tipo.
Para declarar um parâmetro Unbounded Wildcard, liste o? só.
Exemplo
O exemplo a seguir ilustra como extends é usado para especificar um curinga ilimitado.
package com.tutorialspoint;
import java.util.Arrays;
import java.util.List;
public class GenericsTester {
public static void printAll(List<?> list) {
for (Object item : list)
System.out.println(item + " ");
}
public static void main(String args[]) {
List<Integer> integerList = Arrays.asList(1, 2, 3);
printAll(integerList);
List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
printAll(doubleList);
}
}
Isso produzirá o seguinte resultado -
Resultado
1
2
3
1.2
2.3
3.5
O ponto de interrogação (?), Representa o curinga, significa tipo desconhecido em genéricos. Pode haver momentos em que você queira restringir os tipos de tipos que podem ser passados para um parâmetro de tipo. Por exemplo, um método que opera em números pode aceitar apenas instâncias de Integer ou suas superclasses como Number.
Para declarar um parâmetro Wildcard de limite inferior, liste o?, Seguido pela palavra-chave super, seguida por seu limite inferior.
Exemplo
O exemplo a seguir ilustra como super é usado para especificar um curinga de limite inferior.
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
public static void addCat(List<? super Cat> catList) {
catList.add(new RedCat());
System.out.println("Cat Added");
}
public static void main(String[] args) {
List<Animal> animalList= new ArrayList<Animal>();
List<Cat> catList= new ArrayList<Cat>();
List<RedCat> redCatList= new ArrayList<RedCat>();
List<Dog> dogList= new ArrayList<Dog>();
//add list of super class Animal of Cat class
addCat(animalList);
//add list of Cat class
addCat(catList);
//compile time error
//can not add list of subclass RedCat of Cat class
//addCat(redCatList);
//compile time error
//can not add list of subclass Dog of Superclass Animal of Cat class
//addCat.addMethod(dogList);
}
}
class Animal {}
class Cat extends Animal {}
class RedCat extends Cat {}
class Dog extends Animal {}
Isso produzirá o seguinte resultado -
Cat Added
Cat Added
Os curingas podem ser usados de três maneiras -
Upper bound Wildcard-? estende o tipo.
Lower bound Wildcard-? super Type.
Unbounded Wildcard -?
Para decidir qual tipo de curinga melhor se adapta à condição, vamos primeiro classificar o tipo de parâmetros passados para um método como in e out parâmetro.
in variable- Uma variável in fornece dados para o código. Por exemplo, copiar (src, dest). Aqui src atua como uma variável sendo dados a serem copiados.
out variable − An out variable holds data updated by the code. For example, copy(src, dest). Here dest acts as in variable having copied data.
Guidelines for Wildcards.
Upper bound wildcard − If a variable is of in category, use extends keyword with wildcard.
Lower bound wildcard − If a variable is of out category, use super keyword with wildcard.
Unbounded wildcard − If a variable can be accessed using Object class method then use an unbound wildcard.
No wildcard − If code is accessing variable in both in and out category then do not use wildcards.
Example
Following example illustrates the above mentioned concepts.
package com.tutorialspoint;
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
//Upper bound wildcard
//in category
public static void deleteCat(List<? extends Cat> catList, Cat cat) {
catList.remove(cat);
System.out.println("Cat Removed");
}
//Lower bound wildcard
//out category
public static void addCat(List<? super RedCat> catList) {
catList.add(new RedCat("Red Cat"));
System.out.println("Cat Added");
}
//Unbounded wildcard
//Using Object method toString()
public static void printAll(List<?> list) {
for (Object item : list)
System.out.println(item + " ");
}
public static void main(String[] args) {
List<Animal> animalList= new ArrayList<Animal>();
List<RedCat> redCatList= new ArrayList<RedCat>();
//add list of super class Animal of Cat class
addCat(animalList);
//add list of Cat class
addCat(redCatList);
addCat(redCatList);
//print all animals
printAll(animalList);
printAll(redCatList);
Cat cat = redCatList.get(0);
//delete cat
deleteCat(redCatList, cat);
printAll(redCatList);
}
}
class Animal {
String name;
Animal(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name);
}
}
class RedCat extends Cat {
RedCat(String name) {
super(name);
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
}
This will produce the following result −
Cat Added
Cat Added
Cat Added
Red Cat
Red Cat
Red Cat
Cat Removed
Red Cat
Generics are used for tighter type checks at compile time and to provide a generic programming. To implement generic behaviour, java compiler apply type erasure. Type erasure is a process in which compiler replaces a generic parameter with actual class or bridge method. In type erasure, compiler ensures that no extra classes are created and there is no runtime overhead.
Type Erasure rules
Replace type parameters in generic type with their bound if bounded type parameters are used.
Replace type parameters in generic type with Object if unbounded type parameters are used.
Insert type casts to preserve type safety.
Generate bridge methods to keep polymorphism in extended generic types.
Java Compiler replaces type parameters in generic type with their bound if bounded type parameters are used.
Example
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
integerBox.add(new Integer(10));
doubleBox.add(new Double(10.0));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("Double Value :%s\n", doubleBox.get());
}
}
class Box<T extends Number> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
In this case, java compiler will replace T with Number class and after type erasure,compiler will generate bytecode for the following code.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box doubleBox = new Box();
integerBox.add(new Integer(10));
doubleBox.add(new Double(10.0));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("Double Value :%s\n", doubleBox.get());
}
}
class Box {
private Number t;
public void add(Number t) {
this.t = t;
}
public Number get() {
return t;
}
}
In both case, result is same −
Output
Integer Value :10
Double Value :10.0
Java Compiler replaces type parameters in generic type with Object if unbounded type parameters are used.
Example
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
In this case, java compiler will replace T with Object class and after type erasure,compiler will generate bytecode for the following code.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box stringBox = new Box();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box {
private Object t;
public void add(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
In both case, result is same −
Output
Integer Value :10
String Value :Hello World
Java Compiler replaces type parameters in generic type with Object if unbounded type parameters are used, and with type if bound parameters are used as method parameters.
Example
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
printBox(integerBox);
printBox1(stringBox);
}
private static <T extends Box> void printBox(T box) {
System.out.println("Integer Value :" + box.get());
}
private static <T> void printBox1(T box) {
System.out.println("String Value :" + ((Box)box).get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
In this case, java compiler will replace T with Object class and after type erasure,compiler will generate bytecode for the following code.
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box stringBox = new Box();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
printBox(integerBox);
printBox1(stringBox);
}
//Bounded Types Erasure
private static void printBox(Box box) {
System.out.println("Integer Value :" + box.get());
}
//Unbounded Types Erasure
private static void printBox1(Object box) {
System.out.println("String Value :" + ((Box)box).get());
}
}
class Box {
private Object t;
public void add(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
In both case, result is same −
Output
Integer Value :10
String Value :Hello World
Using generics, primitive types can not be passed as type parameters. In the example given below, if we pass int primitive type to box class, then compiler will complain. To mitigate the same, we need to pass the Integer object instead of int primitive type.
Example
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
//compiler errror
//ReferenceType
//- Syntax error, insert "Dimensions" to complete
ReferenceType
//Box<int> stringBox = new Box<int>();
integerBox.add(new Integer(10));
printBox(integerBox);
}
private static void printBox(Box box) {
System.out.println("Value: " + box.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
This will produce the following result −
Output
Value: 10
A type parameter cannot be used to instantiate its object inside a method.
public static <T> void add(Box<T> box) {
//compiler error
//Cannot instantiate the type T
//T item = new T();
//box.add(item);
}
To achieve such functionality, use reflection.
public static <T> void add(Box<T> box, Class<T> clazz)
throws InstantiationException, IllegalAccessException{
T item = clazz.newInstance(); // OK
box.add(item);
System.out.println("Item added.");
}
Example
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args)
throws InstantiationException, IllegalAccessException {
Box<String> stringBox = new Box<String>();
add(stringBox, String.class);
}
public static <T> void add(Box<T> box) {
//compiler error
//Cannot instantiate the type T
//T item = new T();
//box.add(item);
}
public static <T> void add(Box<T> box, Class<T> clazz)
throws InstantiationException, IllegalAccessException{
T item = clazz.newInstance(); // OK
box.add(item);
System.out.println("Item added.");
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
This will produce the following result −
Item added.
Using generics, type parameters are not allowed to be static. As static variable is shared among object so compiler can not determine which type to used. Consider the following example if static type parameters were allowed.
Example
package com.tutorialspoint;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
printBox(integerBox);
}
private static void printBox(Box box) {
System.out.println("Value: " + box.get());
}
}
class Box<T> {
//compiler error
private static T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
As stringBox and integerBox both have a stared static type variable, its type can not be determined. Hence static type parameters are not allowed.
Casting to a parameterized type is not allowed unless it is parameterized by unbounded wildcards.
Box<Integer> integerBox = new Box<Integer>();
Box<Number> numberBox = new Box<Number>();
//Compiler Error: Cannot cast from Box<Number> to Box<Integer>
integerBox = (Box<Integer>)numberBox;
To achive the same, unbounded wildcards can be used.
private static void add(Box<?> box) {
Box<Integer> integerBox = (Box<Integer>)box;
}
Because compiler uses type erasure, the runtime does not keep track of type parameters, so at runtime difference between Box<Integer> and Box<String> cannot be verified using instanceOf operator.
Box<Integer> integerBox = new Box<Integer>();
//Compiler Error:
//Cannot perform instanceof check against
//parameterized type Box<Integer>.
//Use the form Box<?> instead since further
//generic type information will be erased at runtime
if(integerBox instanceof Box<Integer>) { }
Arrays of parameterized types are not allowed.
//Cannot create a generic array of Box<Integer>
Box<Integer>[] arrayOfLists = new Box<Integer>[2];
Because compiler uses type erasure, the type parameter is replaced with Object and user can add any type of object to the array. And at runtime, code will not able to throw ArrayStoreException.
// compiler error, but if it is allowed
Object[] stringBoxes = new Box<String>[];
// OK
stringBoxes[0] = new Box<String>();
// An ArrayStoreException should be thrown,
//but the runtime can't detect it.
stringBoxes[1] = new Box<Integer>();
A generic class is not allowed to extend the Throwable class directly or indirectly.
//The generic class Box<T> may not subclass java.lang.Throwable
class Box<T> extends Exception {}
//The generic class Box<T> may not subclass java.lang.Throwable
class Box1<T> extends Throwable {}
A method is not allowed to catch an instance of a type parameter.
public static <T extends Exception, J>
void execute(List<J> jobs) {
try {
for (J job : jobs) {}
// compile-time error
//Cannot use the type parameter T in a catch block
} catch (T e) {
// ...
}
}
Parâmetros de tipo são permitidos em uma cláusula throws.
class Box<T extends Exception> {
private int t;
public void add(int t) throws T {
this.t = t;
}
public int get() {
return t;
}
}
Uma classe não pode ter dois métodos sobrecarregados que podem ter a mesma assinatura após o apagamento do tipo.
class Box {
//Compiler error
//Erasure of method print(List<String>)
//is the same as another method in type Box
public void print(List<String> stringList) { }
public void print(List<Integer> integerList) { }
}