泛型
使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性
使用泛型之前,ArrayList类只维护一个Object引用的数组,这导致
- 获取一个值时必须进行强制类型转换
- 可以向数组列表中添加任何类的对象
JAVA SE 7 后 构造函数中可以省略泛型类型
类型参数的好处:使得程序具有更好的可读性和安全性
1.泛型类
泛型类就是具有一个或多个类型变量的类。
- 泛型类可以有多个类型变量 例如:public class Pair< T,U >{ … }
2.泛型方法
- 泛型方法的类型变量放在修饰符的后面,返回类型的前面
- 泛型方法可以定义在普通类中,也可以定义在泛型类中
- 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型,但大多数情况下,编译器可以推断出调用的方法的参数类型,所以可以省略< >类型参数
3.类型变量的限定
有时,类或方法需要对类型变量加以约束,例如:
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;
}
}
但是这里有一个问题,变量smallest的类型为T,这意味着它可以是任何一个类的对象,那又如何确定T所属的类有compareTo方法呢
解决这个问题的方案是将T限制为实现了Comparable接口的类,可以通过对类型变量T设置限定来实现这一点
public static <T extends Comparable> T min(T[] a)
4.泛型代码和虚拟机
虚拟机没有泛型类型对象,所有对象都属于普通类
1> 类型擦除
使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定的变量用Object)
- 在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 < T > 则会被转译成普通的 Object 类型,如果指定了上限如 < T extends String> 则类型参数就被替换成类型上限。
2> 翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。例如:
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
擦除getFirst 的返回类型后将返回Objec类型。编译器自动插入Employee的强制类型转换。也就是说,编译器把这个方法调用翻译成两条虚拟机指令
对原始方法 Pair.getFirst 的调用
将返回的Object 类型强制转化为Employee 类型
3> 桥方法(bridge method)
由于在类型擦除后类中会缺少某些必须的方法,这时就由编译器来动态生成这些方法。
例如:
public class MyString implements Comparable<String>{
public int compareTo(String str){
return 0;
}
}
上述代码会出现一个问题,就是在类型擦除之后,实际上类变成了:
public class MyString implements Comparable
这样的话,类MyString就会有编译错误因为没有实现接口Comparable中声明的 int compareTo(Object) 方法,这个时候就由编译器来动态生成这个方法。
5.约束与局限性
1> 不能用基本类型实例化类型参数
不能用类型参数代替基本类型。因此,没有Pair< double>,只有Pair< Double>。这是因为类型擦除,擦出后,Pair类含有Object类型的域,而Object不能存储double值
2> 运行时的类型查询只适用于原始类型
下面是一个典型的例子
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
结果是什么呢?答案是true
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型
- 所以在试图查询一个对象是否属于某个泛型类型时,倘若使用instanof会得到一个编译器错误,如果使用泛型类型进行强制类型转换会的得到一个警告
- 同样的道理,getClass方法总是返回原始类型
3> 不能创建泛型数组
需要说明的是,只是不允许创建这些数组,而声名类型为Pair< String >[] 的变量仍是合法的。不过不能用 new Pair< String >[10] 来初始化这个变量
4> 在可变参数列表中使用泛型
这里有个例子:
public static <T> void addAll(Collection<T>coll, T... ts){
for(t:ts){
coll.add(t);
}
}
应该记得,可变参数ts实际上是一个数组,包含提供的所有实参
现在这样调用:
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll<table,pairs,pair2>;
为了调用这个方法,虚拟机必须建立一个Pair< String >数组,这就违反了前面的规则。不过对于这种情况,规则会有所放松,你会饿到一个警告而不是错误。
可以用注解 @SuppressWarnings(“unchecked”) 或者 @SafeVarargs直接标注addAll方法
5> 不能实例化类型变量
不能使用像new T(…),new T[…] 或T.class这样的表达式中的类型变量。
6> 泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量
7> 不能抛出或捕获泛型类的实例
6通配符类型
这里我在上一篇文章中写了比较详细的解析,链接:
Java—泛型与通配符
至于反射与泛型的知识我们以后再分享