1. Definição e funções dos genéricos
Genéricos são um mecanismo de programação que permite o uso de tipos parametrizados ao escrever código para obter segurança de tipo em tempo de compilação. A seguir está o papel dos genéricos:
-
Melhore a legibilidade e a capacidade de manutenção do código : ao usar parâmetros genéricos em seu código, você pode torná-lo mais claro, legível e fácil de manter.
-
Segurança de código aprimorada : os genéricos podem verificar tipos em tempo de compilação, evitando erros de conversão de tipo em tempo de execução.
-
Maior capacidade de reutilização de código : os genéricos permitem escrever código comum em diferentes tipos de dados, aumentando assim a capacidade de reutilização do código.
-
Simplifique o código : o uso de genéricos pode simplificar o código, evitando escrever códigos semelhantes repetidamente.
Resumindo, os genéricos são um mecanismo de programação poderoso que pode ajudar os desenvolvedores a escrever códigos mais legíveis, fáceis de manter, seguros e reutilizáveis.
Os genéricos Java executam o apagamento de tipo (Type Erasure) durante a compilação. Apagamento de tipo significa que quando o compilador compila código genérico, ele apagará o tipo genérico para seu tipo original ou tipo qualificado, de modo que o tipo genérico não exista em tempo de execução. Ao executar o apagamento de tipo, o compilador substitui os parâmetros de tipo genérico por seu tipo delimitador (qualificado) (se houver) ou pelo tipo Object se nenhum tipo qualificado for especificado.
Por exemplo, uma classe genérica é definida da seguinte forma:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
Após a compilação, o apagamento de tipo apagará o tipo genérico T para o tipo Objeto, e o bytecode gerado é o seguinte:
public class Box {
private Object content;
public void setContent(Object content) {
this.content = content;
}
public Object getContent() {
return content;
}
}
Deve-se notar que embora o tipo genérico não exista em tempo de execução, o compilador ainda executa a verificação de tipo no tipo genérico durante a compilação para garantir a segurança do tipo.
2. Uso de genéricos
1. Os genéricos são definidos na classe
código mostrado abaixo:
class CommonUtil {
public Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
Ao usá-lo, o código é o seguinte:
public static void main(String[] args) {
CommonUtil tool = new CommonUtil<>();
tool.setObj(2);
Integer val1 = (Integer)tool.getObj();
tool.setObj("hello java");
String val2 = (String)tool.getObj();
}
Como pode ser visto no código acima, a conversão forçada é necessária sempre que o valor é obtido. Erros de conversão forçada podem ocorrer facilmente se você não tomar cuidado. Então, existe uma maneira de evitar a coerção de tipo? A resposta é: genéricos. Ao definir genéricos em uma classe, você pode evitar a conversão de tipo. O código melhorado é o seguinte:
class CommonUtil<T> {
public T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
Ao criar uma instância, passe o tipo específico. O código é o seguinte:
public static void main(String[] args) {
CommonUtil<Integer> tool = new CommonUtil<>();
tool.setObj(2);
Integer val1 = tool.getObj();
CommonUtil<String> tool = new CommonUtil<>();
tool.setObj("hello java");
String val2 = tool.getObj();
}
2. Método de definição genérica
Há um ponto não tão bom sobre a definição genérica da classe. Dê uma olhada no exemplo a seguir. O código é o seguinte:
class CommonUtil<T> {
public void show(T obj){
System.out.println("obj = " + obj);
}
}
O código de uso do código é o seguinte:
CommonUtil<Integer> tool1 = new CommonUtil<>();
tool1.show(value);
CommonUtil<String> tool2 = new CommonUtil<>();
tool2.show("111");
CommonUtil<Person> tool2 = new CommonUtil<>();
tool2.show(new Person());
Você descobriu que toda vez que você chama o método show(), você precisa criar uma instância, porque você pode especificar o tipo de parâmetro específico ao criar. Mas criar objetos dessa maneira é muito problemático. Como resolver isso? Você pode definir genéricos nos métodos. O código é o seguinte:
class CommonUtil {
public <W> void show(W obj){
System.out.println("obj = " + obj);
}
}
O código de uso é o seguinte:
CommonUtil tool1 = new CommonUtil<>();
tool1.show(value);
tool1.show("111");
tool1.show(new Person());
Finalmente, o tipo de parâmetro específico é passado quando o método é chamado, para que a criação repetida de objetos possa ser evitada e um método possa ser chamado repetidamente, o que é muito semelhante ao Object. Métodos estáticos , incluindo modificação estática , também podem ser usados. código mostrado abaixo:
class CommonUtil {
public static <W> void show(W obj){
System.out.println("obj = " + obj);
}
}
O código de uso é o seguinte:
CommonUtil.show(value);
CommonUtil.show("111");
CommonUtil.show(new Person());
No entanto, ao definir genéricos em métodos estáticos, observe que este método estático não pode usar genéricos na classe. Por que? Como o tipo de parâmetro específico é especificado quando a classe é criada e o método estático é carregado na JVM antes da classe ser instanciada, não há como saber qual tipo de parâmetro específico sua classe passou ao criar uma instância. Exemplos de erros são os seguintes:
class CommonUtil<W> {
public static <W> void show(W obj){
System.out.println("obj = " + obj);
}
}
3. Os genéricos são definidos na interface
Às vezes, os genéricos são definidos em interfaces e o código é o seguinte:
public interface Inter<T> {
public void print(T obj);
}
Os genéricos na interface podem ser especificados na subclasse ou não. O tipo específico especificado pela subclasse é o seguinte:
public InterImpl implements Inter<String> {
// 接口上的方法
public void print(String obj){
}
// 子类独有方法
protect void show(Object obj){
}
}
Quando em uso, o código é o seguinte:
Inter<String> inter = new InterImpl();
inter.print("hello");
InterImpl impl = new InterImpl();
impl.show(new Object());
Ou defina genéricos em subclasses e interfaces para definir tipos específicos. O código é o seguinte:
public InterImpl<W> implements Inter<String> {
// 接口上的方法
public void print(String obj){
}
// 子类独有方法
protect void show(W obj){
}
}
Quando em uso, o código é o seguinte:
Inter<String> inter = new InterImpl();
inter.print("hello");
InterImpl<Integer> impl = new InterImpl();
impl.show(new Integer(10));
Se o tipo específico não for conhecido na subclasse, você também pode definir um genérico e passar a subclasse genérica para a interface. O código é o seguinte:
public InterImpl<W> implements Inter<W> {
// 接口上的方法
public void print(W obj){
}
// 子类独有方法
protect void show(W obj){
}
}
Quando em uso, o código é o seguinte:
Inter<String> inter = new InterImpl();
inter.print("hello");
InterImpl<Integer> impl = new InterImpl();
impl.show(new Integer(10));
4. Curingas genéricos
Por exemplo, o código é o seguinte:
public class FanxinDemo {
public static void print(Collection<String> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
print(list1);
}
}
Pode-se descobrir que o método print() só pode gerar tipos de parâmetros de String, mas não outros parâmetros. Como esse código pode ser compartilhado? Aqui apresentamos um curinga genérico ?
. Quando você não sabe que tipo é um genérico, você pode usar esta representação temporária. O código é o seguinte:
public class FanxinDemo {
public static void print(Collection<?> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
print(list1);
print(list2);
}
}
Mas suponha que você não queira que o método print() seja usado por parâmetros do tipo Integer, o que você deve fazer? Isto requer qualificação genérica.
5. Limitação genérica
A qualificação genérica pode limitar o intervalo do tipo de parâmetro. ?
Esse intervalo de nível é muito grande e inseguro. O intervalo é limitado pelas palavras-chave extends e super. No entanto, essas duas palavras-chave só podem ser de herança única, portanto, também são limitadas. Existem dois tipos de restrições: disponibilizá-lo para todas as classes pai e disponibilizá-lo para todas as subclasses.
5.1. Disponibilize-o para todas as classes principais
código mostrado abaixo:
public static void print(Collection<? super Integer> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
Indica que todas as classes pai de Integer podem usar o método print().
5.2. Disponibilize-o para todas as subclasses
código mostrado abaixo:
public static void print(Collection<? extends String>> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
Indica que todas as subclasses String podem usar o método print().