Fale sobre os genéricos do Java

Estou participando do "Programa Nuggets · Sail"

visão geral

O que são genéricos? Por que você precisa de genéricos? Como usar? Qual é o princípio? Como melhorar? Esta é basicamente a maneira correta de aprendermos uma tecnologia. Este artigo irá elaborar na ordem acima e apresentar os genéricos que conheço.

o que é genérico

A essência dos genéricos é um tipo parametrizado , ou seja, especificar um parâmetro para o tipo e, em seguida, especificar o valor específico do parâmetro ao usá-lo, para que o tipo possa ser determinado ao usá-lo. Esse tipo de parâmetro pode ser usado em classes, interfaces e métodos e é chamado de classes genéricas, interfaces genéricas e métodos genéricos, respectivamente.

Por que você precisa de genéricos

O principal objetivo da introdução de genéricos em Java é avançar o trabalho de verificação de tipo para o tempo de compilação e entregar o trabalho de **type cast (cast)** para o compilador, para que você possa obter exceções de conversão de tipo e remover o código-fonte em tempo de compilação Digite o código de coerção em . Por exemplo

objeto de efeito genérico

Existem três maneiras de usar genéricos: classes genéricas, interfaces genéricas e métodos genéricos.

classe genérica

Especificar parâmetros ao declarar uma classe constitui uma classe genérica. Por exemplo, no código a seguir, é especificado Tcomo um parâmetro de tipo, então esse tipo pode ser usado nesta classe. Por exemplo, declare Ta variável do tipo name, declare To parâmetro formal do tipo parame assim por diante.

public class Generic<T> {
    public T name;
    public Generic(T param){
        name=param;
    }
    public T m(){
        return name;
    }
}

Então, ao usar uma classe, você pode passar o tipo correspondente para criar instâncias de tipos diferentes, pois o código a seguir passa em String, Integer, Boolean3 tipos, respectivamente:

private static void genericClass()
 {
     Generic<String> str=new Generic<>("总有刁民想害朕");
     Generic<Integer> integer=new Generic<>(110);
     Generic<Boolean> b=new Generic<>(true);

     System.out.println("传入类型:"+str.name+"  "+integer.name+"  "+b.name);
}

A saída é:传入类型:总有刁民想害朕 110 true

Se não houver nenhum tipo genérico, precisamos definir três classes para obter o efeito acima ou uma classe contendo três construtores e três métodos de valor.

interface genérica

A definição de interface genérica é basicamente a mesma de classe genérica

public interface Generator<T> {
    public T produce();
}

método genérico

Isso é relativamente complicado. Quando entrei em contato pela primeira vez, também fiquei confuso. Depois de entender as características, não é tão difícil.

public class Generic<T> {
    public T name;
    public  Generic(){}
    public Generic(T param){
        name=param;
    }
    public T m(){
        return name;
    }
    public <E> void m1(E e){ }
    public <T> T m2(T e){ }
}

重点看public <E> void m1(E e){ }这就是一个泛型方法,判断一个方法是否是泛型方法关键看方法返回值前面有没有使用<>标记的类型,有就是,没有就不是。这个<>里面的类型参数就相当于为这个方法声明了一个类型,这个类型可以在此方法的作用块内自由使用。 上面代码中,m()方法不是泛型方法,m1()m2()都是。值得注意的是m2()方法中声明的类型T与类申明里面的那个参数T不是一个,也可以说方法中的T隐藏了类型中的T。下面代码中类里面的T传入的是String类型,而方法中的T传入的是Integer类型。

Generic<String> str=new Generic<>("总有刁民想害朕");
str.m2(123);

通配符?

?代表任意类型,例如有如下函数:

public void m3(List<?>list){
    for (Object o : list) {
        System.out.println(o);
    }
}

其参数类型是,那么我们调用的时候就可以传入任意类型的List,如下

str.m3(Arrays.asList(1,2,3));
str.m3(Arrays.asList("总有刁民","想害","朕"));

但是说实话,单独一个意义不大,因为大家可以看到,从集合中获取到的对象的类型是Object 类型的,也就只有那几个默认方法可调用,几乎没什么用。如果你想要使用传入的类型那就需要强制类型转换,这是我们接受不了的,不然使用泛型干毛。其真正强大之处是可以通过设置其上下限达到类型的灵活使用,且看下面分解重点内容

通配符上界

通配符上界使用<? extends T>的格式,意思是需要一个T类型或者T类型的子类,一般T类型都是一个具体的类型,例如下面的代码。

public void printIntValue(List<? extends Number> list) {  
    for (Number number : list) {  
        System.out.print(number.intValue()+" ");   
    }  
}

这个意义就非凡了,无论传入的是何种类型的集合,我们都可以使用其父类的方法统一处理。

通配符下界

通配符下界使用<? super T>的格式,意思是需要一个T类型或者T类型的父类,一般T类型都是一个具体的类型,例如下面的代码。

public void fillNumberList(List<? super Number> list) {  
    list.add(new Integer(0));  
    list.add(new Float(1.0));  
}

至于什么时候使用通配符上界,什么时候使用下界,在**《Effective Java》中有很好的指导意见:遵循PECS原则,即producer-extends,consumer-super.** 换句话说,如果参数化类型表示一个生产者,就使用 <? extends T>;如果参数化类型表示一个消费者,就使用<? super T>

Java泛型原理解析

为什么人们会说Java的泛型是伪泛型呢,就是因为Java在编译时擦除了所有的泛型信息,所以Java根本不会产生新的类型到字节码或者机器码中,所有的泛型类型最终都将是一种原始类型,那样在Java运行时根本就获取不到泛型信息。

擦除

Java编译器编译泛型的步骤:

  1. 检查泛型的类型 ,获得目标类型
  2. 擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换)
  3. 调用相关函数,并将结果强制转换为目标类型。

如何擦除: 当擦除泛型类型后,留下的就只有原始类型了,例如上面的代码,原始类型就是ArrayList。擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换),如下所示

擦除之前:

//泛型类型  
class Pair<T> {    
    private T value;    
    public T getValue() {    
        return value;    
    }    
    public void setValue(T  value) {    
        this.value = value;    
    }    
}

擦除之后:

//原始类型  
class Pair {    
    private Object value;    
    public Object getValue() {    
        return value;    
    }    
    public void setValue(Object  value) {    
        this.value = value;    
    }    
}

因为在Pair<T>中,T是一个无限定的类型变量,所以用Object替换。如果是Pair<T extends Number>,擦除后,类型变量用Number类型替换。

Gson是如何把泛型转换成目标类型的

TypeToken

/**
 * Represents a generic type {@code T}. Java doesn't yet provide a way to
 * represent generic types, so this class does. Forces clients to create a
 * subclass of this class which enables retrieval the type information even at
 * runtime.
 *
 * <p>For example, to create a type literal for {@code List<String>}, you can
 * create an empty anonymous inner class:
 *
 * <p>
 * {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
 *
 * <p>This syntax cannot be used to create type literals that have wildcard
 * parameters, such as {@code Class<?>} or {@code List<? extends CharSequence>}.
 *
 * @author Bob Lee
 * @author Sven Mawson
 * @author Jesse Wilson
 */
public class TypeToken<T> {
  final Class<? super T> rawType;
  final Type type;
  final int hashCode;

  /**
   * Constructs a new type literal. Derives represented class from type
   * parameter.
   *
   * <p>Clients create an empty anonymous subclass. Doing so embeds the type
   * parameter in the anonymous class's type hierarchy so we can reconstitute it
   * at runtime despite erasure.
   */
  @SuppressWarnings("unchecked")
  protected TypeToken() {
    this.type = getSuperclassTypeParameter(getClass());
    this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);
    this.hashCode = type.hashCode();
  }
  ......
}
public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
  if (json == null) {
    return null;
  }
  return (T) fromJson(new JsonTreeReader(json), typeOfT);
}
//只要调用fromJson传入一个TypeToken的子类就能获取到泛型的类型
new Gson().fromJson(json,new TypeToken<ArrayList<String>>(){}.getType());

原理

子类如果实例化这个泛型,则编译时会保留实例化类型,可以通过反射获取,如:

Generic<String> generic = new Generic<String>(){ };
System.out.println(generic.getClass().getGenericSuperclass());

以上类型会输出:com.hencoder.layoutlayout.Generic<java.lang.String>

Acho que você gosta

Origin juejin.im/post/7233720284708044859
Recomendado
Clasificación