要学习java泛型,我觉得首先得知道设计的目的;第一,不用强制类型转换;第二,能够编译时检查,更加安全;第三,代码可读性增加。
简单的泛型使用
泛型类的定义和使用
泛型类就是具有有个或者多个类型变量的类。
public class Pair<T>{
private T first;
private T second;
public Pair(){
}
public Pair(T first,T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public void setFirst(T first){
this.first = first;
}
}
//泛型使用
Pair<String> pair = new Pair<>();
Pair<String> pair = new Pair<>("Hello","world");
pair.setFirst("Hello")
泛型方法
泛型方法可以定义在泛型类中,也可以定义在普通类中。
class ArrayAlg {
public static <T> T getMiddle(T ...a){
return a[a.length/2];
}
}
//使用
ArrayAlg.getMiddle("hello","Test","Q");
ArrayAlg.<String>getMiddle("hello","Test","Q");
通常使用第一种方式调用,能够这样调用的原因是,有足够的参数能够推断出T代表的含义,而且编译器会自动推策。
类型变量的限定
有时候我们需要对类或者方法中的类型变量加以约束;如:
class ArrayAlg{
public static <T> T min(T[] a){
if(a==null || a.length == 0) return null;
T smallest = a[0];
for(int i =1;i<a.length;i++){
if(smallest.compareTo(a[i])>0) smallest = a[i];
}
return smallest;
}
}
这里就出现了一个问题,我们必须让T实现了Compareable接口,所以可以通过类型限定T extends Conpareable
如果要实现多个,就可以使用T extends Conpareable & Serializable
泛型代码和虚拟机
我们不仅仅需要知道使用泛型,我们还需要知道泛型在java虚拟机中的表现,我觉得首先我们需要知道这三点:
- 在虚拟机中没有泛型类型对象——所有的对象都是普通类。
- 无论何时定义一个泛型类型,都自动提供一个相应的原始类型,原始类型的名字就是删去类型参数的泛型类型名。
- 擦除类型变量,并替换成限定类型(无限定类型的变量就用Object)
例如Pair的原始类型:
public class Pair{
private Object first;
private Object second;
public Pair(Object first,Object second){
this.first = first;
this.second = second;
}
public Object getFirst(){
return first;
}
public void setFirst(Object first){
this.first = first;
}
}
如果存在两个限定类型,那么取第一个。
import java.io.Serializable;
public class Interval<T extends Comparable & Serializable> implements
Serializable {
private T lower;
private T upper;
public Interval (T lower, T upper) {
if(first.compareTo(second)<=0){
this.lower = first;
this.upper = second;
}else {
this.lower = second;
this.upper = first;
}
}
}
//原始类
import java.io.Serializable;
public class Interval implements Serializable {
private Comparable lower;
private Comparable upper;
public Interval (Comparable lower, Comparable upper) {
if(first.compareTo(second)<=0){
this.lower = first;
this.upper = second;
} else {
this.lower = second;
this.upper = first;
}
}
}
如果第一个类写的是Serializable,那么编译器在必要是插入强制转换成Comparable;为了提高效率,标签接口应该放在末尾。
翻译泛型表达式
在虚拟机中调用泛型方法,由于擦除了返回类型,所有编译器会插入强制类型转换。
Pair<Employee> buddlies = ...
Employee employee = buddlies.getFirst()
这段代码,编译器会在自动在插入强制转换类型,在getFirst返回object 对象时。
实际在虚拟机中会翻译成两条虚拟机指令:
- 对原始方法getFirst的调用
- 对Object对象强制转换成Employee对象
翻译泛型方法
由于在java虚拟机中没有泛型,所有的泛型都没擦除,所以对于泛型方法就容易出现多态问题。
我们从一个例子中来看这个问题
class DateInterval extends Pair<Date>{
...
public void setSecond(Date second){
if(second.compareTo(getFirst())>=0){
super.setSecond(second);
}
}
}
我们这段代码是希望复写setSecond方法,使得第二个对象始终小于第一个对象。对于DateInterval 类编译后,泛型类型被擦除,就会出现两个不同的setSecond方法。
public void setSecond(Date second)
public void setSecond(Object second)
那么当代码中出现:
DateInterval interval = new DateInterval();
Pair<Date> pair = interval;
pair.setSecond(aDate);
这时候调用的是setSecond(Object Object)方法,因为向上转换成了Pair对象,所以会重新生成一个桥方法复写setSecond(Object Object)方法,具体:
public void setSecond(Object second){
setSecond((Date)second)
}
java 泛型的几点
- 虚拟机内没有泛型,只有普通类和方法
- 所有的类型都用限定类型替换
- 桥方法合成,用来保持多态
- 为了保持类型安全,必要时插入强制类型转换
泛型的约束与局限性
- 不能用基本类型来当泛型参数,比如Pair是不合法的,但是可以Pair,基本类型的包装类可以
- 类型查询只能查询原始类型:对于 a instanceof Pair, a instanceof Pair都不合法;
- 不允许实例化参数化数组,Pair[] table = new Pair[10];对于table,由于类型擦除,table[0]= new Pair()也是能够通过检查,所以还是会导致类型错误,所以不允许实例化参数化数组,但是可以声明Pair[]
- 不能实例化类型参数,如不能使用new T、new T[] 或者T.class;因为类型擦除后这些都是无效的
- 不能使用带有类型参数的静态方法和静态域
泛型的继承规则
其实记住两点就行:
- 无论S或者T是什么关系,Pair< S >和Pair< T >都是没有关系的;
- 泛型类可以实现拓展其他泛型类;普通类继承泛型类不一定是泛型类。
通配符类型
对于通配符类型我觉得最重要的一点是和类型参数区分开;
类型参数是在定义泛型类或者方法是时候使用,而通配符是在使用泛型时使用;
class Pair<T>{
public void setFirst(T t){}
}
//通配符
public static void printBuddies(Pair<? extends Employee>)
通配符 Pair<? extends Employee>是 Pair<Manager> 的父类型
,原因是? extends Employee
是一个整体,这是给泛型类型赋的一种值,表示的是一种 Employee 子类的泛型,这不是一种具体的类型,只是对类型加了限定;因为Manage是 Employee的子类,所以是父子关系
对于代码:
Pair<Manager> managerBuddies = new Pair<>(ceo,cfo);
Pair<? extends Employee> wildCardBuddies = managerBuddies;
wildCardBuddies.setFirst(lowlyEmployee);//编译报错
编译器报错的原因:? extends Employee
只能保证的是泛型是 Employee的子类,但不能确定是哪一种具体的类型,为了保证安全,不出现类型转换错误,所以编译报错。我们应该将? extends Employee
当一个整体看,这是一种泛型类型参数的一种值的形式。但对于get方法不会报错,因为子类可以转化成父类
通配符还有一种限定方式? super Manager
,这个表示的泛型是Manager类的父类。
同上:set方法不会报错,因为子类可以转化成父类,get方法会报错,父类不能转成子类。
对于无限定的通配符?
可以理解为? extends Object