Hable acerca de los genéricos de Java

Estoy participando en el "Programa Nuggets · Sail"

descripción general

¿Qué son los genéricos? ¿Por qué necesita genéricos? ¿cómo utilizar? ¿Cuál es el principio? ¿Cómo mejorar? Esta es básicamente la forma correcta de aprender una tecnología. Este artículo se desarrollará en el orden anterior e introducirá los genéricos que conozco.

que es generico

La esencia de los genéricos es un tipo parametrizado , es decir, especificar un parámetro para el tipo y luego especificar el valor específico del parámetro al usarlo, de modo que el tipo pueda determinarse al usarlo. Este tipo de parámetro se puede utilizar en clases, interfaces y métodos, y se denomina clases genéricas, interfaces genéricas y métodos genéricos, respectivamente.

¿Por qué necesita genéricos?

El propósito principal de introducir genéricos en Java es adelantar el trabajo de verificación de tipos al momento de la compilación, y entregar el trabajo de **type cast (cast)** al compilador, para que pueda obtener excepciones de conversión de tipos y eliminar el código fuente en tiempo de compilación Escriba el código de coerción en . Por ejemplo

objeto de efecto genérico

Hay tres formas de usar genéricos: clases genéricas, interfaces genéricas y métodos genéricos.

clase genérica

Especificar parámetros al declarar una clase constituye una clase genérica. Por ejemplo, en el siguiente código, se especifica Tcomo un parámetro de tipo, entonces este tipo se puede usar en esta clase. Por ejemplo, declare Tla variable del tipo name, declare Tel parámetro formal del tipo param, etc.

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

Luego, al usar una clase, puede pasar el tipo correspondiente para crear instancias de diferentes tipos, ya que el siguiente código pasa 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);
}

La salida es:传入类型:总有刁民想害朕 110 true

Si no hay un tipo genérico, necesitamos definir tres clases para lograr el efecto anterior, o una clase que contenga tres constructores y tres métodos de valor.

interfaz genérica

La definición de interfaz genérica es básicamente la misma que la de clase genérica

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

método genérico

Esto es relativamente complicado. Cuando entré en contacto con él por primera vez, también estaba confundido. Después de comprender las características, no es tan 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>

Supongo que te gusta

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