花了好几个小时看十页的泛型简介……人已经麻了,理解后赶紧把要点简单记下来,备忘用。由于笔者仅是初学,如有错误欢迎评论区留言。
注:以下的例子完全可以举一反三,举出例子并用相同或类似的原因解释为什么可以运行或者不可以运行。
零、泛型擦除
泛型擦除的原理:编译时将类/方法中使用的泛型擦除,替换为原始类型(rawType)。一般地,普通无继承的泛型默认继承自Object,故编译时一般替换为Object。
至于声明时传入的类型,编译器将自动识别并阻止不匹配的元素传入泛型类中。说到底,泛型仅仅是在编译层面解析,底层仍旧是原始类型进行接收。而返回时,底层的原始类型对之前传入的实际类型参数(<xx>)进行强制转换,实现取出。
举个例子:某小区有两个垃圾桶,原本两个桶无差别的,干湿垃圾可以随便放后来进行垃圾分类了,居委会主任规定:1号桶放干垃圾,2号桶放湿垃圾。但总有人会记错,把湿垃圾放入1号桶,导致垃圾工人收垃圾时被溅了一身后来主任搞了一台湿度检测仪,丢进去之前检测一下垃圾湿度,不符合的就不让放进去本质上1、2号垃圾桶的内部结构没有任何变化,我们只是被湿度检测仪拦截了。
作者:bravo1988
链接:https://zhuanlan.zhihu.com/p/255264414
Java的泛型擦除也决定了其泛型仅仅是假泛型。其泛型实现方式只是编译器的语法糖罢了。
一、泛型的转换问题
看下面两行代码:
List<String> a = new ArrayList<Integer>(); //编译报错
List<Integer> b = new ArratList<String>(); //编译报错
这两行代码是编译器禁止的引用传递。泛型的初衷就是解决类型转换的问题,在编译器层面直接禁止了这种写法,不必深究为什么不行。
再看两行代码:
List<Integer> a = new ArrayList(); //编译警告
List b = new ArrayList<Integer>(); //编译警告
原因引用下文:
第一种情况,可以实现与完全使用泛型参数一样的效果,第二种则没有效果。因为类型检查就是编译时完成的,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。
链接:Java 基础 - 泛型机制详解 | Java 全栈知识体系 (pdai.tech)
不过实际测试中第二种也能用嘛。
二、泛型通配符<?>
这个东西就有意思多了。若在声明引用中使用<?>,则不能使用:在方法内部泛型类的参数和返回值为泛型的方法。举个例子:
public class App{
public static void main(String args[]){
Position<String> position = new Position<String>();
position.setX("11");
position.setY("22");
System.out.println(position.getX());
}
public void fun(Position<?> tmp){
String str = tmp.getX(); //Type mismatch: cannot convert from capture#1-of ? to String
}
}
class Position<T>{
private T x;
private T y;
public void setX(T x){
this.x = x;
}
public void setY(T y){
this.y = y;
}
public T getX(){
return this.x;
}
public T getY(){
return this.y;
}
}
这个比较容易理解了:创建tmp对象时传入的是实际类型参数是通配符<?>,对应的Position类中T均为?,很明显方法不能使用。
但是!如果tmp调用方法时传入参数填入null,也是可以使用的。不过为了防止混淆,强烈不建议使用这种写法。