Herança de Generics em Java
Em Java, classes genéricas podem fazer parte de hierarquias de herança: podem estender outras classes ou servir como classes base para subclasses. Essa combinação amplia a flexibilidade dos generics com o reuso e a especialização de código.
Classe base genérica
Quando uma classe genérica é estendida, a subclasse precisa indicar o tipo que será usado na classe base.
class Account<T> {
private T id;
T getId() { return id; }
Account(T id) {
this.id = id;
}
}
class DepositAccount<T> extends Account<T> {
DepositAccount(T id) {
super(id);
}
}No construtor de DepositAccount, há uma chamada ao construtor da classe base (super), recebendo o valor do tipo definido.
Exemplo de uso:
DepositAccount dAccount1 = new DepositAccount(20);
System.out.println(dAccount1.getId());
DepositAccount dAccount2 = new DepositAccount("12345");
System.out.println(dAccount2.getId());Subclasse genérica com parâmetros adicionais
Uma subclasse genérica pode declarar parâmetros de tipo próprios, além dos herdados da classe base.
class Account<T> {
private T id;
T getId() { return id; }
Account(T id) {
this.id = id;
}
}
class DepositAccount<T, S> extends Account<T> {
private S name;
S getName() { return name; }
DepositAccount(T id, S name) {
super(id);
this.name = name;
}
}Exemplo de uso:
DepositAccount<Integer, String> dAccount1 = new DepositAccount<>(20, "Tom");
System.out.println(dAccount1.getId() + " : " + dAccount1.getName());
DepositAccount<String, Integer> dAccount2 = new DepositAccount<>("12345", 23456);
System.out.println(dAccount2.getId() + " : " + dAccount2.getName());Subclasse não genérica herdando de genérica
Uma subclasse pode não ser genérica e especificar diretamente o tipo que será usado na classe base.
class Account<T> {
private T id;
T getId() { return id; }
Account(T id) {
this.id = id;
}
}
class DepositAccount extends Account<Integer> {
DepositAccount() {
super(5);
}
}Aqui, DepositAccount fixa o tipo Integer para a classe base. Assim, o construtor de Account receberá obrigatoriamente um valor desse tipo.
Exemplo de uso:
DepositAccount dAccount1 = new DepositAccount();
System.out.println(dAccount1.getId());Subclasse genérica de classe não genérica
Também é possível ter o caso inverso: a classe base não é genérica, mas a subclasse é.
class Account {
private String name;
String getName() { return name; }
Account(String name) {
this.name = name;
}
}
class DepositAccount<T> extends Account {
private T id;
T getId() { return id; }
DepositAccount(String name, T id) {
super(name);
this.id = id;
}
}Nesse caso, a herança segue as regras comuns, mas a subclasse adiciona flexibilidade com seu próprio tipo genérico.
Conversão entre tipos genéricos compatíveis
É possível converter objetos entre classes genéricas relacionadas, desde que usem o mesmo parâmetro de tipo.
class Account<T> {
private T id;
T getId() { return id; }
Account(T id) {
this.id = id;
}
}
class DepositAccount<T> extends Account<T> {
DepositAccount(T id) {
super(id);
}
}Conversão válida:
DepositAccount<Integer> depAccount = new DepositAccount<>(10);
Account<Integer> account = (Account<Integer>) depAccount;
System.out.println(account.getId());Conversão inválida:
DepositAccount<Integer> depAccount = new DepositAccount<>(10);
Account<String> account = (Account<String>) depAccount; // ErroNão é permitido converter tipos genéricos com parâmetros diferentes, pois isso viola a segurança de tipos do Java.
Resumo
- Classes genéricas podem herdar de outras classes genéricas ou não genéricas.
- Uma subclasse pode declarar novos parâmetros de tipo além dos herdados.
- É possível criar subclasses não genéricas que fixam o tipo usado na classe base.
- Também é possível ter uma subclasse genérica de uma classe base não genérica.
- Conversões entre tipos genéricos só funcionam se os parâmetros de tipo forem iguais.