Java之泛型浅析(generic type)

以下都是个人理解,若有错误,请多多批评

1. 例子

先定义如下继承关系

class C{}
class A {
        public void say() {
            System.out.println("I am A");
        }
}

class B extends A {
        public void say() {
            System.out.println("I am B");
        }
}

早期版本的Java代码(1):

List list = new ArrayList();
list.add(123);
list.add("abc");
//o到底是什么类型?不清楚....可能我们本意是放入数字,但不知被谁放入了字符串,容易引发bug
Object o = list.get(0); 

之后引入了泛型,代码(2)变成了这样:

List list1 = new ArrayList();
list1.add(123);
list1.add("abc"); // error ! error !
//此时o是Integer
Integer o = list1.get(0);

很多时候我们想定义一种List,这种List里只能包含一个继承体系内的对象,比如只能包含Number,代码(3)如下:

//list3中只能放Number的子类或Number本身
List list3 = new ArrayList();
//下面这句为何会报错?Integer是Number的子类呀。
//List实际上声明了一个list,这个list中可以放入Number的某一个子类型,Integer或Long或其他均可;但是并不能直接向里面add;
//试想,如果add(Integer)可以,那么add(Long)肯定也行,这样和早期没有泛型的代码有何区别?
//换句话说,此时list里的类型并不能确定,所以不能add具体的类型进去.
list3.add(new Integer(1)); //error !  
//get()是可以的,因为里面的类型至少能确定是Number的。
Number number = list3.get(0);

List list4 = new ArrayList();
//放入B肯定是正确的
list4.add(new B());
//此时的list4可以放入B的某一个父类,但不能确定是A
list4.add(new A()); //error !
Object object = list4.get(0);//类型又不确定了

如上所述,ListList都只是对类型范围进行了限定,list中具体是哪种类型是不确定的。在java中,若想自由的add和get,list中的类型必须如代码(2)所示的那样,确定类型。因此,这种用法一般会在方法中来限定方法参数,代码(4)如下:

//定义一个方法, 这个方法第一个参数接收一个list,这个list里要么是T,要么是T的一个父类型
private  void addElement(List list, T t){
        list.add(t);
}
List list2 = new ArrayList();//包含具体类型的list
//可以直接list2.add(new A())和list2.add(new B())
//确定类型的list传入方法,
addElement(list2, new A());
addElement(list2, new B());
list2.get(0).say(); //I am A
list2.get(1).say(); //I am B

List list3 = new ArrayList();
addElement(list3, new C());

2. 协变和逆变

简单得说,协变是把子(窄)类型赋值给父(宽)类型,逆变则相反。

List list1 = new ArrayList(); // ok,多态的实质:父类的引用指向具体的一个子类实例
List list2 = new ArrayList(); // ok,此时左侧可以有泛型,右侧没有泛型
List list21 = new ArrayList(); // ok

List list3 = new ArrayList(); // error,泛型不支持协变
List list4 = new ArrayList(); // ok,使泛型也可以进行协变

List list5 = new ArrayList(); // error,泛型不支持逆变
List list6 = new ArrayList(); // ok,使泛型支持了逆变
list6.add(new Integer(1)); // ok
list6.add(new Long(1)); // error
List list61 = new ArrayList(); // ok
list61.add(new Integer(1)); // ok
list61.add(new Float(1.2f)); // ok 
//?super AAA,只要是在AAA的继承图谱中,子类或者父类都可以add

3. 泛型数组

首先数组是协变的

Number[] numbers = new Integer[1]; // ok
List[] list = new ArrayList[1]; // ok

但是,不能创建泛型数组

new ArrayList[1] // error
List[] lsa = new List[10] // ok, 这时相当于没有泛型

Example
下面的代码编译没问题,但是运行期报错了

//List[] lsa = new List[10]; // error.
List[] lsa = new List[10]; // ok,这不是泛型数组
Object[] oa = lsa; //移花接木,运用协变性,将lsa赋给父类型
List li = new ArrayList();
li.add(new Integer(3)); // list中放入了Integer
oa[1] = li;
String s = lsa[1].get(0); // error: ClassCastException.

上面代码的本意是创建一个只能装入String的数组,但我们使用一点技术手段将Integer装入,运行时就会出错,这就是不能创建泛型数组的最主要原因。

采用通配符的方式是允许的。虽然这种方式是可以的,但是需要显示转换类型,违背了泛型设计的初衷:添加泛型的一个重要目的就是消除这种显示的转换,而这种代码又必须添加。

List[] lsa = new List[10]; // ok
Object[] oa = lsa;
List li = new ArrayList();
li.add(new Integer(3));
oa[1] = li; 
Integer i = (Integer) lsa[1].get(0); // ok,需要强制转换
System.out.println(i);

List list = new ArrayList<>();
list.add("hahaha");
oa[2] = list;
String o1 = (String) lsa[2].get(0); 
System.out.println(o1);

4. ?和T的区别

逻辑上List可以看成是所有List,List等的父类。T代表某种具体类型。很多时候二者是等价的。

//下述两个方法是相同的,不能同时出现在一个类中
//这种方式定义方法可以消除重复性
public void test(List list){} 
public  void test(List list){}

5. instanceof

list instanceof ArrayList // error. 这样使用时,可能是想判断list是否是盛着Number的Arraylist,但是Number会被擦除掉,因此不能对确切的泛型使用
list instanceof ArrayList // ok,相当与list instanceof ArrayList。其目的应该是判断list是否是Arraylist

6. 类型擦除

我们都知道,编译后泛型会被擦除,那我们怎样在运行期获取这些泛型信息呢?
Java 引入泛型擦除的原因是避免因为引入泛型而导致运行时创建不必要的类。

(1)如下例子,两个class相同,泛型的不同并没有导致生成不同的Class类。

Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
System.out.println(c1 == c2); //true

(2)定义如下类,查看一下字节码

class Generic_2{
   List list = new ArrayList();  //1
   List list2 = new ArrayList();  //2

   public void test(T t) {
       List list3 = new ArrayList();    //3
       list3.add(1); //4
   }
}

运行命令:java -v Generic_2.class,下面为部分信息

java.util.List list;  //1 
    descriptor: Ljava/util/List;
    flags:
    Signature: #13                          // Ljava/util/List;  //有泛型信息

  java.util.List list2;  //2
    descriptor: Ljava/util/List;
    flags:
    Signature: #15                          // Ljava/util/List;  //有泛型信息

  public void test(T);  //test方法
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."":()V
         7: astore_2
         8: aload_2
         9: iconst_1
        10: invokestatic  #6                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: invokeinterface #7,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z  //没有泛型信息了
        18: pop
        19: return
      LineNumberTable:
        line 34: 0
        line 35: 8
        line 36: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   Lcom/young/generic/Generic_2;
            0      20     1     t   Ljava/lang/Object;
            8      12     2 list3   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   Lcom/young/generic/Generic_2;
            0      20     1     t   TT;
            8      12     2 list3   Ljava/util/List;
    Signature: #32                          // (TT;)V  //有泛型信息
}

上述字节码其实说明了泛型擦除后,其实只是擦除了Code属性里字节码指令相关的泛型信息,Signature这些元数据信息中其实依然保留了泛型信息,这就是我们可以通过反射获取泛型信息的根本原因

(3)定义了几个类来获取泛型信息:

class A{}
class B extends A{}

interface C{}
class D implements C{}

public static void main(String[] args){
        //extends
        Type type = B.class.getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) type;
        for (Type type1 : parameterizedType.getActualTypeArguments()) {
            System.out.println(type1);
        }
        
        //implements
        ParameterizedType parameterizedType1 = (ParameterizedType) D.class.getGenericInterfaces()[0];//0代表只有一个接口
        for (Type type1 : parameterizedType1.getActualTypeArguments()) {
            System.out.println(type1);
        }

        //getTypeParameters()只能获取声明时的泛型名字
        TypeVariable>[] typeParameters = new A().getClass().getTypeParameters();
        for (TypeVariable> typeParameter : typeParameters) {
            System.out.println(typeParameter.getTypeName());
        }
}
/**output
class java.lang.String
class java.lang.Integer

class java.lang.String

K
V
**/

7. 几个例子

Collections.java

// sort方法是既要从list中取,也要把结果放入list,这种即get又set的方法,必须用确定的类型T
public static > void sort(List list){...};

//fill方法只会set而不会get,所以要用super
public static  void fill(List list, T obj) {...}

//这个方法只会get而不会set,所以要用extends
public static  int binarySearch(List> list, T key) {...}

本文参考了
http://swiftlet.net/archives/1950
https://www.cnblogs.com/lzq198754/p/5780426.html
https://my.oschina.net/lifany/blog/875769

你可能感兴趣的:(Java之泛型浅析(generic type))