JAVA基础系列规划:
- JAVA基础(1)——基本概念
- JAVA基础(2)——数据类型
- JAVA基础(3)——容器(1)——常用容器分类
- JAVA基础(4)——容器(2)——普通容器
- JAVA基础(5)——并发(1)——总体认识
- JAVA基础(6)——并发(2)——原子
- JAVA基础(7)——并发(3)——锁机制
- JAVA基础(8)——并发(4)——线程池
- JAVA基础(9)——容器(3)——并发容器
- JAVA基础(10)——IO、NIO
- JAVA基础(11)——泛型
- JAVA基础(12)——反射
- JAVA基础(13)——序列化
- JAVA基础(14)——网络编程
先看这样一段小程序:
public class GenericType {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add("hello world!");
String s = al.get(0); //编译不通过
System.out.println(s);
}
}
当我们满怀喜悦地运行时,却发现程序并不能通过编译。一拍脑门,原来ArrayList的get方法定义为返回Object,要把Object赋给String,必须进行强制类型转换,好吧:
String s = (String)al.get(0); //编译通过
bingo!运行通过,”hello world!”
如果ArrayList中存储的不是String类型的元素,强制类型转换的时候就会出现ClassCastException的错误。
一方面要求强制类型转换,另一方面强制类型转换可能出错,是不是很头疼,你必须在使用数据时清楚地知道其数据类型,才能完成一次完美的强制类型转换。
你可能会想,很简单啊,问题就出在不知道容器里面数据的类型,要是能把容器中数据类型固定下来,这样就可以不使用强制转型了,也就不会有错误出现了。
你想的没错,这就是泛型要做的事情。
1 泛型原理
泛型告诉编译器想使用什么类型,实现编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException,举个栗子:
public class GenericType {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("content");
String s = al.get(0); //编译通过
System.out.println(s);
}
}
这段代码指定了容器ArrayList中数据的类型为String(称这种方式为泛型),但实际上,编译器在编译时去掉了泛型中的类型信息(称这个过程为类型擦除),所以在运行时不存在任何类型相关的信息。
类型擦除的基本过程也比较简单:将类型参数替换为原始类型,同时去掉出现的类型声明,产生可能需要的桥接方法。类型擦除时,类型参数被限定类型(无限定的变量用Object)替换,同时去掉出现的类型声明,即去掉<>的内容。
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
变为:
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
如果指定了类型参数的上界的话,则使用这个上界。
若public class Pair< T extends Comparable&Serializable>,则原始类型就是Comparable。
若public class Pair< T extends Serializable&Comparable> ,则原始类型就用Serializable替换,而编译器在必要的时要向Comparable插入强制类型转换。为了提高效率,应该将标签接口(即没有方法的接口)放在边界限定列表的末尾。
既然说类型变量会在编译的时候擦除掉,那为什