为什么使用泛型程序设计
泛型程序设计意味编写的代码可以对多种不同类型的对象重用。
泛型类
泛型类就是有一个或多个类型变量的类。
一般,类型变量定义在类名的后面,用尖括号(<>)括起来,泛型类也可以有多个类型变量。
如以下定义方式:
单个类型变量
public class Pair<T>{
}
多个类型变量
public class Pair<T, U>{
}
类型变量在整个类定义中用于指定方法的返回类型以及字段和局部变量的类型。
常见的做法是类型变量使用大写字母。Java库使用变量E表示集合的元素类型,K和V分别表示表的键和值的类型。T(必要时还可以使用相邻的字母U和S)表示“任意类型”。
泛型方法
泛型方法是一个带有类型参数的方法,可以在普通类中定义,也可以在泛型类中定义。
类型变量放在修饰符后面,并放在返回值的前面。
class ArrayAlg{
public static <T> T getMiddel(T... t){
return t[t.length / 2];
}
}
类型变量限定
有时候,类或方法需要对类型变量加以约束。可以通过对类型变量设置一个限定(bound)来实现。
记法如下,表示T应该是限定类型的子类型, T和限定类型可以是类,也可以是接口:
<T extends BoundingType>
举例说明:
以下代码无法通过编译。变量min的类型为T,这表示它可能是任何一个类的对象。但如何知道T所属的类有一个compareTo方法呢?
public static <T> T min(T[] arr){
if(arr == null || arr.length <= 0){
return null;
}
T min = arr[0];
for (T t : arr){
if(min.compareTo(t) > 0){
min = t;
}
}
return min;
}
解决方法:
对T进行限制,限制T只能是实现了Comparable接口的类
public static <T extends Comparable> T min(T[] arr){
if(arr == null || arr.length <= 0){
return null;
}
T min = arr[0];
for (T t : arr){
if(min.compareTo(t) > 0){
min = t;
}
}
return min;
}
一个类型变量或通配符也可以有多个限定,限定类型用&分隔,而逗号用来分隔类型变量。如:
T extends Comparable & Serializable
泛型代码和虚拟机
泛型擦除
参考:https://www.cnblogs.com/wuqinglong/p/9456193.html
Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
如在代码中定义List<Integer>和List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法发现在运行时刻出现的类型转换异常的情况。
对于Pair<T>, 其原始类型如下:
public class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
在Pair<T>中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair<String>或Pair<Integer>,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。
如声明public class Pair<T extends Comparable>, 则其原始类型为Comparable。
转换泛型表达式
由于擦除了返回类型,编译器会插入强制类型转换。
Pair\<Employee> buddies = new Pair\<>();
Employee value = buddies.getValue;
getValue擦除类型后返回的是Object,编译器自动插入转换到Employee的强制类型转换,也就是这个方法调用转换为两条虚拟机指令:
1.对原始方法Pair.getValue的调用
2.将返回的Object类型转换为Employee类型。
转换泛型方法
类型擦除也会出现在泛型方法中。
如public static <T extends Comparable> T min(T[] a)是整个一组方法,
在类型擦除之后,只剩下一个方法,变为了:
public static <Comparable> min(Comparable[] a)
在多态问题中,如:
class DateInterval extends Pair<LocalDate>{
public void setValue(LocalDate value){
...
}
}
在类型擦除后变为
class DateInterval extends Pair{ //after erasure
public void setValue(LocalDate value){
...
}
}
并且还有另一个从Pair中继承的setValue方法,即
public void setValue(Object value)
在执行下列语句时,我们希望调用setValue时具有多态性,应该调用DateInterval.setValue():
DateInterval dateInterval = new DateInterval();
Pair<LocalDate> pair = dateInterval;
pair.setValue(aDate);
但是在类型擦除后,与多态发生冲突,编译器通过在DateInterval类中生成一个桥方法(bridge method)来解决。
public void setValue(Object value){
setValue((LocalDate) value);
}
在DateInterval类中也覆盖了getValue()方法,因此会有两个getValue()方法:
LocalDate getValue(); // defined in DateInterval
Object getValue(); // override the method defined in Pair
两个方法有相同的参数类型,这种编写是不合法的。但是,在java虚拟机中会由参数类型和返回类型共同指定一个方法。因此,编译器可以为两个仅返回类型不同的方法生成相应的字节码,虚拟机可以正确处理这种情况。