含义:
- 泛型(Generic type)是Java 语言中一种支持创建按类型进行参数化的类。
// 使用泛型之前
Map map = new HashMap();
map.put("key", "value");
String s = (String) map.get("key");
- 上述代码中,Map.get() 的返回值类型为Object,所以需要强制类型转换。但是这就要求码农们人为的确保类型转换的正确性,否则会出现ClassCastException。
- 如果希望在代码编译检查阶段,能够自动发现上述可能存在的错误,并且不用人为地去进行强制类型转换的话,就要用到泛型了。
好处:
- 泛型的主要目的是提高 Java 程序的类型安全。
泛型允许编译器检查类型错误。类型错误会在编译时被捕获,而不会在运行时报 ClassCastException的错误。将类型检查从运行时改为编译时有助于找到程序的错误,提高程序的可靠性。
比较下面两个例子:
// 不使用泛型:
List list = new ArrayList();
list.put(new Integer(5));
Integer i = (Integer) list.get(0);
// 使用泛型:
List<Integer> list = new ArrayList<Integer>();
// Java1.7以上可以写为:
// List<Integer> list = new ArrayList<~>();
list.put(new Integer(5));
Integer i = list.get(0);
- JDK 5.0 中集合框架中的 Map 接口:
public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
...
}
- 其中的类型参数 K和V可以看成是指定类型的占位符,取决于我们申明变量时传递的参数类型。如下:
Map<String, String> map = new HashMap<String, String>();
map.put("key", "value");
// 使用泛型时,不需强制类型转换,因为编译器知道get()方法将返回一个String类型的值。
String s = map.get("key");
向后兼容:
从 JDK 5.0 开始,标准类库中的许多类,比如集合框架,都泛型化了。但是对于 JDK 5.0 之前版本,其代码不用修改就可以运行于5中,只是这样就得不到泛型的好处了。
正是为了保证向后兼容,所以Java的泛型并不是真泛型,而是伪泛型。这是与C sharp的一个重要区别。
类型参数命名:推荐使用单个大写字母)
- K => 键
- V => 值
- E => 异常类
- T => 泛型
泛型不是协变的:
- Object类是String类的父类,但是
List<Object>
类不是List<String>
类的父类:
// 有效代码
Integer[] intArray = new Integer[10];
Number[] numberArray = intArray;
// Integer是Number的子类,所以一个Integer数组可以是一个Number数组
// 无效代码
List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // 报错
// 假如上述代码是成立的,那么下面这句代码就无法解释了
numberList.add(new Double(3.1415926));
类型通配符:
- 假设有这样一个方法:
void dosthList(List l) {
for (Object o : l) {
...
}
}
如果试图用将一个List参数传递进去,会出现警告:破坏使用泛型的类型安全。
- 如果将该方法改写为如下所示:
void dosthList(List<Object> l) {
for (Object o : l) {
...
}
}
仍然不会通过编译,因为对于泛型不存在协变,一个List<Integer>
不是 一个List<Object>
!
- 使用类型通配符:
void dosthList(List<?> l) {
for (Object o : l) {
...
}
}
上面代码中的问号是一个类型通配符。List<?>
是任何 List泛型的父类型,所以可以将 List<Object>
、List<Integer>
传递给该方法。
有限制类型:
- 有时我们需要对类型参数指定约束,例如有一个类Jiarenyf类,使用类型参数 V,我们希望该参数由Number类来限制,即:
public class Jiarenyf<V extends Number> { ... }
- 此时编译器允许创建
Jiarenyf<Integer>
或Jiarenyf<Float>
类型的变量,但会出现错误如果定义了Jiarenyf<String>
类型的变量。也即,类型V必须为Number类的子类。在没有类型限制时,类型参数为Object类的子类。所以在List<?>
调用get()方法时返回Object类型的值。