Restrições de Generics em Java
Em Java, ao declarar um parâmetro de tipo em um recurso genérico, ele pode, por padrão, representar qualquer tipo. No entanto, há situações em que é necessário restringir esse parâmetro para que aceite apenas determinados tipos. Essas restrições permitem especificar uma classe base ou interface que o tipo genérico deve seguir.
Para criar uma restrição, adiciona-se a palavra extends
após o nome do parâmetro de tipo, seguida da classe ou interface que definirá o limite. Por exemplo:
class Account { }
class Transaction<T extends Account> { }
Nesse caso, o parâmetro T
de Transaction
está restrito à classe Account
. Isso significa que T
pode ser Account
ou qualquer classe que a estenda.
Exemplo com classe base
O código a seguir demonstra um cenário em que a classe Transaction
representa a transferência de valores entre duas contas, sendo parametrizada com T
restrito a Account
. Assim, o compilador reconhece que os objetos de T
têm os métodos definidos em Account
.
public class Program {
public static void main(String[] args) {
Account acc1 = new Account("1876", 4500);
Account acc2 = new Account("3476", 1500);
Transaction<Account> tran1 = new Transaction<>(acc1, acc2, 4000);
tran1.execute();
tran1 = new Transaction<>(acc1, acc2, 4000);
tran1.execute();
}
}
class Transaction<T extends Account> {
private T from;
private T to;
private int sum;
Transaction(T from, T to, int sum) {
this.from = from;
this.to = to;
this.sum = sum;
}
public void execute() {
if (from.getSum() > sum) {
from.setSum(from.getSum() - sum);
to.setSum(to.getSum() + sum);
System.out.printf("Account %s: %d \nAccount %s: %d \n",
from.getId(), from.getSum(), to.getId(), to.getSum());
} else {
System.out.printf("Operation is invalid");
}
}
}
class Account {
private String id;
private int sum;
Account(String id, int sum) {
this.id = id;
this.sum = sum;
}
public String getId() { return id; }
public int getSum() { return sum; }
public void setSum(int sum) { this.sum = sum; }
}
Se a restrição não fosse aplicada, o compilador não garantiria que T
possui os métodos de Account
, resultando em erro de compilação.
Restrições com tipos genéricos parametrizados
As restrições também podem envolver classes genéricas que possuem seus próprios parâmetros. No exemplo abaixo, Transaction
é limitada a tipos Account<String>
, garantindo que o identificador da conta seja do tipo String
.
public class Program {
public static void main(String[] args) {
Account<String> acc1 = new Account<>("1876", 4500);
Account<String> acc2 = new Account<>("3476", 1500);
Transaction<Account<String>> tran1 = new Transaction<>(acc1, acc2, 4000);
tran1.execute();
tran1 = new Transaction<>(acc1, acc2, 4000);
tran1.execute();
}
}
class Transaction<T extends Account<String>> {
private T from;
private T to;
private int sum;
Transaction(T from, T to, int sum) {
this.from = from;
this.to = to;
this.sum = sum;
}
public void execute() {
if (from.getSum() > sum) {
from.setSum(from.getSum() - sum);
to.setSum(to.getSum() + sum);
System.out.printf("Account %s: %d \nAccount %s: %d \n",
from.getId(), from.getSum(), to.getId(), to.getSum());
} else {
System.out.printf("Operation is invalid");
}
}
}
class Account<T> {
private T id;
private int sum;
Account(T id, int sum) {
this.id = id;
this.sum = sum;
}
public T getId() { return id; }
public int getSum() { return sum; }
public void setSum(int sum) { this.sum = sum; }
}
Restrições com interfaces
Além de classes, uma restrição também pode ser definida com interfaces. Nesse caso, o tipo passado para o parâmetro genérico precisa implementar a interface indicada, garantindo que possua seus métodos.
public class Program {
public static void main(String[] args) {
Account acc1 = new Account("1235rwr", 5000);
Account acc2 = new Account("2373", 4300);
Transaction<Account> tran1 = new Transaction<>(acc1, acc2, 1560);
tran1.execute();
}
}
interface Accountable {
String getId();
int getSum();
void setSum(int sum);
}
class Account implements Accountable {
private String id;
private int sum;
Account(String id, int sum) {
this.id = id;
this.sum = sum;
}
public String getId() { return id; }
public int getSum() { return sum; }
public void setSum(int sum) { this.sum = sum; }
}
class Transaction<T extends Accountable> {
private T from;
private T to;
private int sum;
Transaction(T from, T to, int sum) {
this.from = from;
this.to = to;
this.sum = sum;
}
public void execute() {
if (from.getSum() > sum) {
from.setSum(from.getSum() - sum);
to.setSum(to.getSum() + sum);
System.out.printf("Account %s: %d \nAccount %s: %d \n",
from.getId(), from.getSum(), to.getId(), to.getSum());
} else {
System.out.printf("Operation is invalid");
}
}
}
Restrições múltiplas
É possível aplicar mais de uma restrição ao mesmo parâmetro de tipo. O Java permite que o tipo genérico seja, ao mesmo tempo, subclasse de uma determinada classe e implemente uma ou mais interfaces. Por exemplo:
class Person { }
interface Accountable { }
class Transaction<T extends Person & Accountable> { }
Nesse cenário, o tipo T
precisa herdar de Person
e implementar Accountable
simultaneamente.
Resumo
- Limitam os tipos aceitos por um parâmetro genérico para garantir que possuam certos métodos ou características.
- São definidas com
extends
, podendo indicar uma classe, interface ou ambas. - Permitem que o compilador reconheça e use métodos herdados pelo tipo genérico.
- Podem ser aplicadas em classes, métodos ou parâmetros genéricos.
- Suportam múltiplas restrições combinando classe base e interfaces.