携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
范型
Java范型时JDK5引入的特性,允许在定义类、接口和方法的时候可以使用类型参数。声明的类型参数会在使用的时候替换为具体的类型。
范型是java提供的语法糖,我们所定义的范型在编译期间都会被类型擦除,使用泛型可提高代码的复用性。Java的集合框架都使用了范型,我们平常所定义的List、List这两个集合类型是相同的,在编译的时候会进行类型擦除,擦除后的类型变为原始类型List。
类型擦除(type erasue)
例子
写几个例子,反编译查看
例一:
final List<String> list = new ArrayList<>(10);
list.add("可可爱爱");
System.out.println(list.get(0));
反编译后:
List list = new ArrayList(10);
list.add("\u53EF\u53EF\u7231\u7231");
System.out.println((String)list.get(0));
编译过后List的类型被擦除,也就是说只有List类型,而不存在List这个类型。中间对于元素的具体操作,通过类型转化实现。
例二:
/**
* 指定范型类型,那么CompareTo方法也必须指定类型,如果不指定那么就会替换为其左边界Object
* 编译后Comparable<MyNumericValue>的类型被擦除,变为Comparable<Object>
* 那么直接导致未能实现compareTo方法
* 编译器检测到了,就给生成一个桥接方法compareTo(Object object)
*/
class MyNumericValue implements Comparable<MyNumericValue> {
private int value;
@Override
public int compareTo(MyNumericValue o) {
return this.value - o.value;
}
}
class MyNumericValue2 implements Comparable {
private int value;
@Override
public int compareTo(Object o) {
return 0;
}
}
反编译查看:
class MyNumericValue implements Comparable {
MyNumericValue() {
}
public int compareTo(MyNumericValue o) {
return value - o.value;
}
public volatile int compareTo(Object obj) {
return compareTo((MyNumericValue) obj);
}
private int value;
}
//
class MyNumericValue2 implements Comparable {
MyNumericValue2() {
}
public int compareTo(Object o) {
return 0;
}
private int value;
}
对于一些范型接口的使用,如果接口的抽象方法的入参、出参是范型类型的话,如果在类型擦除时导致未能实现接口方法时,需要编译器生成桥接方法,通过此桥接方法调用原始方法。
例三:
public <T extends List<E>, E extends Comparable<E>> void max(List<E> list) {
E max = list.get(0);
for (E e : list) {
if (e.compareTo(max) > 0) {
max = e;
}
}
System.out.println("最大值=>" + max);
}
反编译查看:
public void max(List list) {
Comparable max = (Comparable) list.get(0);
Iterator iterator = list.iterator();
do {
if (!iterator.hasNext())
break;
Comparable e = (Comparable) iterator.next();
if (e.compareTo(max) > 0)
max = e;
} while (true);
System.out.println((new StringBuilder()).append("\u6700\u5927\u503C=>").append(max).toString());
}
E extends Comparable限定了范型边界,那么首先将所有的范型E替换为最左边界 Comparable,然后进行类型擦除得到最终擦除后的结果 Comparable。
增强for循环底层使用的是迭代器进行遍历。
小结
类型擦除指的是通过类型参数合并,将范型类型实例关联到一份字节码文件上,编译器只为范型类生成一份字节码文件。
类型擦除的过程中jvm会将范型java代码转化为普通java代码,编译器直接操作的是字节码。
范型带来的问题
java的违范型机制,类似于语法糖,它方便了我们编写代码,并提高了代码的可复用性,那么复杂的工作就是由jvm待做。
重载
重载的条件是方法名相同、参数列表不同。那么如果对于相同的方法名,使用List、List,可以进行重载么?
显然是不行的,因为之前我们就说过jvm中只有List这一种类型,并不存在List、List,因为编译会进行类型擦除,
idea直接提示,两个方法拥有相同的范型,编译不可通过。
'method1(List<String>)' clashes with 'method1(List<Integer>)'; both methods have same erasure
public void method1(List<String> list){
}
public void method1(List<Integer> list){
}
范型通配符
常用泛型符号
范型符号都有对应含义,常用范型符号如下:
-
E element (在集合框架中表示元素的意思)
-
T Type java类
-
K key (键值)
-
V value (value值)
-
N number 数值类型
-
? 未知java类型(无限制通配符类型)
对于普通泛型符号(除?以外),必须预先声明类型才可以使用,也就是在我们使用的时候必须确定类型,否则编译报错。

而对于?通配符来说无需事先声明,表示任意类型。正是因为无需声明类型,那么对于编译器来说它不知道如何对类型进行强转,就造成了此方式的泛型无法添加元素,且获取得到的元素也只能是Object类型。
如下List<?> list 表示此方法可以接受任意类型的集合对象
class TestClass2 {
public void test(List<?> list){
//list.add("");
final Object o = list.get(0);
System.out.println(o);
}
public static void main(String[] args) {
final TestClass2 testClass2 = new TestClass2();
testClass2.test(new ArrayList<Integer>(Arrays.asList(1,2)));
testClass2.test(new ArrayList<String>(Arrays.asList("1","2")));
testClass2.test(new ArrayList<Comparable>(Arrays.asList(1,2)));
}
}
限定通配符
对于非限定通配符来说,也有不好的地方,它不限制类型,那么在方法逻辑中想使用指定类型的方法时需要强制转化,如此可能出现
ClassCastException
异常。
class TestClassC<T> {
T t;
public void test(T t1,T t2){
final Comparable t1c = (Comparable) t1;
System.out.println(t1c.compareTo(t2));
}
}
如上我们可以限定test的参数类型为Comparable的实现类。所以java为我们引入了限定通配符
限定通配符对类型进行限制,java中有两种限定通配符:
-
表示上界,<? extends T>
泛型类型必须为T或T的派生类(可以是接口、也可以是子类)这里没有用任何意义上的继承关系。
表示可接收任意T即T的派生类类型。 用于取值。
-
表示下界,<? super T>, 即类型必须为T或T的父类型
泛型类型必须为T或T的父类。
表示可接收任意T即T的父类类型。 用于存值。
指定类型就不行了,因为String不是Fruit的父类类型
关于存取值问题
小结:
如果对于泛型没有限制,并且集合类型支持存取的话,直接使用非限定通配符T E等。
如果对于泛型有限制,则使用限定通配符 extents支持取,super支持存。
?对于取是不友好的,因为?表示任意java类型,那么取出来的一定是Object,而Object没有意义,强转存在ClassCastExpression异常风险。