Java基础——深入理解泛型

1.Java泛型

2.Java泛型如何实现?

Java的泛型是通过类型擦除实现的!即Java的泛型是伪泛型,在编译期间,所有的泛型信息都会被擦除掉。所以Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况。

类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别。:boom:

2.1 Java泛型擦除的证明

  • 类型信息被擦除,原始类型相同 下面两个list容器的参数类型分别是String和Integer,但在运行期间两者的类型信息被擦除,只保留原始类型Object
    public class reflect {
      public static void main(String[] args){
          List<String> list1 = new ArrayList<>(); 
          List<Integer> list2 = new ArrayList<>();
          list1.add("12");
          list2.add(12);
          System.out.println(list1.getClass() == list2.getClass());//true
      }
    }
  • 通过反射添加其他类型也可以证明泛型的类型信息被擦除,留下原始类型!
    public class reflect {
      public static void main(String[] args) throws Exception{
          List<Integer> list1 = new ArrayList<>();
          list1.add(13);
          list1.getClass().getMethod("add",Object.class).invoke(list1,"a123");
          System.out.println(list1.toString());
      }
    }
    输出结果
    [13, a123]

2.2 类型擦除后的原始类型

  • 原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换:boom:

    class info<T>{
      //无限定,原始类型就是Object
    }
    class message<T extends Comparable>{
      //有限定,即Comparable为边界,那么就用这个限定类型
    }
  • 所以在调用泛型类或者方法的时候,可以指定泛型,也可以不指定泛型。:boom:

    • 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
    • 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类

3.类型擦除引起的问题及解决方法

因为种种原因,Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀问题,但是也引起来许多新问题,所以,SUN对这些问题做出了种种限制,避免我们发生各种错误

3.1 编译前检查

:raising_hand:: 既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList< Integer > 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

ArrayList<String> list1 = new ArrayList(); 
list1.add("1"); //编译通过 
list1.add(1); //编译错误 

:information_desk_person:Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

3.2 引用传递

讨论一下情况:

ArrayList<String> list1 = new ArrayList(); //第一种 情况
ArrayList list2 = new ArrayList<String>(); //第二种 情况

list1.add("1"); //编译通过 
list1.add(1); //编译错误 
String str1 = list1.get(0); //返回类型就是String

list2.add("1"); //编译通过 
list2.add(1); //编译通过
Object object = list2.get(0); //返回类型就是Object

我们可以发现,类型的检查是依赖于它的引用的,而无关于它指向对象的类型。即引用限定了什么类型,编译器就用限定的类型进行检查,没限定则泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object:boom::boom::boom:

再来看一种情况:

ArrayList<Object> list2 = new ArrayList<>(); 
list2.add(new Object());
ArrayList<String> list22 = list2;//编译错误

以下为对上面代码个人理解
首先已经限定了list2的类型为Object,则添加的元素都为Object,此时将其指向ArrayList< String > 的引用,则就像向下转型一样,是不允许的!

3.3 自动类型转换

因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?

看下ArrayList.get()方法:

public E get(int index) {  

    RangeCheck(index);  

    return (E) elementData[index];  

}

可以看到,在return之前,会根据泛型变量进行强转。假设泛型类型变量为Date,虽然泛型信息会被擦除掉,但是会将(E) elementData[index],编译为(Date)elementData[index]。所以我们不用自己进行强转。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!