一篇文章搞定java泛型和Type体系

125 阅读7分钟

1.什么是泛型

泛型指的参数化类型。类型可以当做参数传递的特性

2. 类型参数的理解

类型参数是参数,参数的特点有

  • 声明
  • 使用
  • 赋值
//T1,T2就是声明类型参数的地方
class Pair<T1, T2> {
    //使用类型参数的地方
    public T1 first;
    public T2 second;
​
   //使用类型参数的地方
   public Pair(T1 first, T2 second) {
            this.first = first;
            this.second = second;
   }
 }
  @Test
  public void one() {
        //给类型参数赋值的地方
        Pair<String, Integer> pair = new Pair<String,Integer>("test", 1);
  }

2.1 类型参数让人困惑的地方

2.1.1 类型参数和普通参数的区别

比如说一个普通的方法,有一个String类型的参数s

那么就是参数类型是String,参数名是s

void funciont1(String s){
  
}

但是对于类型参数来说,它就只有一个参数名

2.1.2 类型参数的声明和赋值

类型参数是一个参数,它就有声明,赋值,使用,声明和赋值两者要区分开,不然很容易搞混

1.声明类型参数

泛型类
 //T1,T2就是类型参数
 //Pair<T1,T2>是泛型类
class Pair<T1,T2>{
  T[] ts;//泛型数组
}
泛型方法
//T 就是类型参数 
//function1这个方法是泛型方法  
fun <T> void function1(T t){
      
}

2.给类型参数赋值

给类型参数赋值为具体的类型值,

//StringInteger就 实际的类型值
//Pair<String, Integer> 的type是 参数化类型(ParameterizedType)
Pair<String, Integer> pair = new Paire()

3. 泛型的作用

让代码更安全可读性更高

比如说如果没有泛型,向集合中添加元素时,就可以添加任何类型的元素,

从集合中取出元素时,又必须进行类型转化,就很有可能发生类型转化异常

4.泛型擦除

4.0 类型擦除

编译时,类型参数会被替换为其限定类型,如果没有限定类型则替换为Object类型

java类

public class Pair<T1 extends String, T2> {
    public T1 first;
    public T2 second;
​
    public Pair(T1 first, T2 second) {
        this.first = first;
        this.second = second;
    }
​
    public T1 getFirst() {
        return first;
    }
​
    public void setFirst(T1 first) {
        this.first = first;
    }
​
    public T2 getSecond() {
        return second;
    }
​
    public void setSecond(T2 second) {
        this.second = second;
    }
}

使用ASMPlugin查看字节码

T1类型因为声明为String的子类型,所以T1被替换为了String类型

T2类型因为没有被限定,所以被替换为了Object类型

public class com/Pair {
​
  // compiled from: Pair.java
​
  // access flags 0x1
  // signature TT1;
  // declaration: first extends T1
  public Ljava/lang/String; first
​
  // access flags 0x1
  // signature TT2;
  // declaration: second extends T2
  public Ljava/lang/Object; second
​
  // access flags 0x1
  // signature (TT1;TT2;)V
  // declaration: void <init>(T1, T2)
  public <init>(Ljava/lang/String;Ljava/lang/Object;)V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 8 L1
    ALOAD 0
    ALOAD 1
    PUTFIELD com/Pair.first : Ljava/lang/String;
   L2
    LINENUMBER 9 L2
    ALOAD 0
    ALOAD 2
    PUTFIELD com/Pair.second : Ljava/lang/Object;
   L3
    LINENUMBER 10 L3
    RETURN
   L4
    LOCALVARIABLE this Lcom/Pair; L0 L4 0
    // signature Lcom/Pair<TT1;TT2;>;
    // declaration: this extends com.Pair<T1, T2>
    LOCALVARIABLE first Ljava/lang/String; L0 L4 1
    // signature TT1;
    // declaration: first extends T1
    LOCALVARIABLE second Ljava/lang/Object; L0 L4 2
    // signature TT2;
    // declaration: second extends T2
    MAXSTACK = 2
    MAXLOCALS = 3
​
  // access flags 0x1
  // signature ()TT1;
  // declaration: T1 getFirst()
  public getFirst()Ljava/lang/String;
   L0
    LINENUMBER 13 L0
    ALOAD 0
    GETFIELD com/Pair.first : Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/Pair; L0 L1 0
    // signature Lcom/Pair<TT1;TT2;>;
    // declaration: this extends com.Pair<T1, T2>
    MAXSTACK = 1
    MAXLOCALS = 1
​
  // access flags 0x1
  // signature (TT1;)V
  // declaration: void setFirst(T1)
  public setFirst(Ljava/lang/String;)V
   L0
    LINENUMBER 17 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/Pair.first : Ljava/lang/String;
   L1
    LINENUMBER 18 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/Pair; L0 L2 0
    // signature Lcom/Pair<TT1;TT2;>;
    // declaration: this extends com.Pair<T1, T2>
    LOCALVARIABLE first Ljava/lang/String; L0 L2 1
    // signature TT1;
    // declaration: first extends T1
    MAXSTACK = 2
    MAXLOCALS = 2
​
  // access flags 0x1
  // signature ()TT2;
  // declaration: T2 getSecond()
  public getSecond()Ljava/lang/Object;
   L0
    LINENUMBER 21 L0
    ALOAD 0
    GETFIELD com/Pair.second : Ljava/lang/Object;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/Pair; L0 L1 0
    // signature Lcom/Pair<TT1;TT2;>;
    // declaration: this extends com.Pair<T1, T2>
    MAXSTACK = 1
    MAXLOCALS = 1
​
  // access flags 0x1
  // signature (TT2;)V
  // declaration: void setSecond(T2)
  public setSecond(Ljava/lang/Object;)V
   L0
    LINENUMBER 25 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/Pair.second : Ljava/lang/Object;
   L1
    LINENUMBER 26 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/Pair; L0 L2 0
    // signature Lcom/Pair<TT1;TT2;>;
    // declaration: this extends com.Pair<T1, T2>
    LOCALVARIABLE second Ljava/lang/Object; L0 L2 1
    // signature TT2;
    // declaration: second extends T2
    MAXSTACK = 2
    MAXLOCALS = 2
}
​

注意泛型T extends 类型1,类型2,那么它会被替换为离T最近的泛型1

4.1 桥方法

桥方法是用于继承泛型类时保证多态的。

使用父类引用 引用子类实例,如果没有桥方法,子类是无法覆盖父类带有类型参数的方法的

class Node<T> {
   
​
    public T data;
​
    public Node(T data) {
    this.data = data; }
​
    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}
​
class MyNode extends Node<Integer> {
   
    public MyNode(Integer data) {
    super(data); }
​
    //Node<T> 泛型擦除后为 setData(Object data),而子类 MyNode 中并没有重写该方法,所以编译器会加入该桥方法保证多态
    public void setData(Object data) {
        setData((Integer) data);
    }
​
   //这个setData并没有覆盖父类的setData方法
    public void setData(Integer data) {
  
        super.setData(data);
    }
}
​
public void main(){
  Node node = new MyNode();
  //如果没有桥方法,那么node.setData就调用的是父类的setData
  node.setData(1)
}

4.2 泛型擦除的残留

类型参数的值被擦除后,还会保留到.class文件中,我们仍然可以通过反射获取类型参数的信息

Integer,String是赋值给Map中类型参数K,V的值,它们被擦除后会保存在.class文件中

image-20221016180510609

而且这里我们也可以注意到,相关的方法叫做getXXXType(),因为我们获取的类型(Type)型信息

考点

1. 泛型的原理

泛型是从JDK5开始支持的,为了向下兼容,java虚拟机是不支持泛型的。

在编译时,会将所有的泛型信息擦除,将所有的泛型类型转化为原始类型。

2. java编译器是如何进行类型擦除的

  1. 将类型参数替换为限定类型

    1. 没有限定类替换为Object类型
    2. 有一个限定类替换为限定类
    3. 有多个限定类替换为第一个第一个限定
  2. 必要时进行类型转化(比如,一个类型有两个限定类,当它当做第二个限定类使用时,要进行类型转化)

  3. 生成桥方法保证多态性

3. 泛型的副作用

3.1 类型参数不能实例化为基本数据类型

以ArrayList为例,类型擦除后泛型参数E会被替换为Object,Object是无法引用基本数据类型的。

image-20221016185022662

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
   public E get(int index) {
        Objects.checkIndex(index, this.size);
        return this.elementData(index);
    }
}

3.2 不能使用instanceof进行泛型类的类型判断

image-20221016185819043

其实可以获取到strings的类型信息 ParameterizedType 看它是否和 “java.util.ArrayList”相等

3.3 类型参数无法在静态成员了静态方法中使用

image-20221016190946589

  1. 方法冲突

       class  ClassA<T>{
         
            //编译后有两个equals(Object obj)方法
            @Override
            public boolean equals(T obj) {
                return super.equals(obj);
            }
        }
    
  2. 不能实例化类型参数

    因为类型擦除后,类型参数就转化为Object.(其实也不是Object,是它的限定类)

    如果允许实例化类型参数,那每次实例化得到的都是Object对象

    image-20221016191719045

  3. 不能实例化泛型数组

    可能会导致类型转化异常

如果实例化泛型数组是允许的

  ```java
  public static <T extends Comparable> T[] minmax(T... a){
    T[] mm = new T[2];//实际这是不行的哈
    return mm
  }
  ```

类型擦除后

public static  Comparable[] minmax(Comparable... a){
  Comparable[] mm = new Comparable[2];
  return mm
}

调用

String[] ss = minmax("Tom","Dick","Harry");//这里会报类型转化错误

这为什么会报错

因为String的父类是Comparable

String[]的父类是Comparable[]

有一个原则是父类引用是无法转化为子类引用的:

如下

image-20221016195721653

5.通配符

5.1 作用

实例化类型参数时,不使用固定的类型, 而是限定类型参数的边界

   @Test
    public void one() {
        //类型参数时Comparable的子类
        List<? extends Comparable> comparables = new ArrayList<String>();
        //类型参数时Interger的父类
        List<? super Integer> integers = new ArrayList<Number>();
    }

6.Type类型体系

Type类型体系是站在泛型的角度描述类的类型的

image-20221017143157153

Type的类型有

  • Class 类
  • TypeVariable 类型参数
  • 泛型数组
  • 参数化类型
  • 通配符类型

看下面的类型

​
    //这个声明在类上的T是类型参数TypeVariable
    class Demo0<T> {
​
        //List的Type为Class
        List list0;
​
        //List<String>的Type为ParameterizedType,其中String为ActualTypeArguments,Type为Class
        List<String> list1;
​
        //List<? extends String>的Type为ParameterizedType,其中? extends String为ActualTypeArguments,
        // ? extends String 的Type为WildCard
        List<? extends String> list2;
​
        //T[]的Type泛型数组 GenericArrayType
        T[] array;
    }

获取Type的方式

@Test
public void test() throws NoSuchFieldException {
​
    TypeVariable typeVariable = Demo0.class.getTypeParameters()[0];
    System.out.println(typeVariable);
​
    Field list0Field = Demo0.class.getDeclaredField("list0");
    System.out.println("list0的类型的type为:" + list0Field.getGenericType().getClass().getName());
​
    Field list1Field = Demo0.class.getDeclaredField("list1");
    Type genericTypeList1 = list1Field.getGenericType();
    ParameterizedType parameterizedTypeList1 = (ParameterizedType) genericTypeList1;
    Type type = parameterizedTypeList1.getActualTypeArguments()[0];
    System.out.println("list1的类型的type为" + parameterizedTypeList1.getClass().getName());
    System.out.println("list1的类型的typed的实际参数类型为 " + type.getClass().getName());
​
    Field list2Field = Demo0.class.getDeclaredField("list2");
    ParameterizedType parameterizedTypeList2 = (ParameterizedType) list2Field.getGenericType();
    WildcardType wildcardType = (WildcardType) parameterizedTypeList2.getActualTypeArguments()[0];
    System.out.println("list2的类型的type为 " + parameterizedTypeList2.getClass().getName());
    System.out.println("list2的类型的type的实际参数类型为" + wildcardType.getClass().getName());
​
    Field array = Demo0.class.getDeclaredField("array");
    System.out.println("泛型数组:"+array.getGenericType().getClass().getName());
​
}