泛型是对代码进行抽象封装的一种手段,例如著名的 C++ 模板就是泛型机制。
不使用泛型
List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3
在代码中进行强制类型转换,既不优雅,又容易出错(runtime error)。
使用泛型
List<Integer>
myIntList = new LinkedList<Integer>(); // 1'
myIntList.add(new Integer(0)); // 2'
Integer x = myIntList.iterator().next(); // 3'
通过泛型的方式,可以使代码准确的表达编程者的意图,避免强制类型转换潜在的运行风险。例如上例中,通过泛型将集合中元素的类型限定为 Integer,不能往集合中添加任何非 Integer 类型的元素,也不允许在集合元素上调用任何非 Integer 类型的方法,否则会出现编码错误。
Java 是一种强类型、静态类型的编程语言,泛型语法的本质是提供一种机制,让编译器在编译代码时获取更多的类型元信息,避免代码在运行态出错。
另一方面,泛型语法也增强了代码的可读性,代码维护人员能更直观的获取对象的类型信息。
想一下,下面的代码合法吗?
List<String> ls = new ArrayList<String>(); // 1
ls.add(new Object()); // 2
第一行,没问题!
第二行,会产生编译错误!
先思考一个问题,List 与 List 是什么关系?直觉上会认为,List 也是 List, 如同 String 对象也是 Object 对象一样。
当然,答案是否定的,为什么呢?因为:
String.class.isAssignableFrom(Object.class) == false;
在面向对象语言中子类对象可以赋值给父类引用,但是父类对象不允许赋值给子类的引用。我们可以将 String 对象添加到 List 链表中,但是不可以将 Object 对象添加到 List 链表中。
在学习泛型时,一定要理解这一点,因为它跟直觉相悖,容易犯错。
所谓的通配符 ?
,可以简单的理解为 type of unknown
,也就是未知类型。可以用它来指代任意类型。
public void showError(Collection<Object> collection){
System.out.println(collection.size());
}
public void showOk(Collection<?> collection){
System.out.println(collection.size());
}
public void test(){
showError(new ArrayList<String>()); // Compile error
showOk(new ArrayList<String>());
}
另外,使用了通配符的集合,只能读,不能改, ?
表示元素类型为 unknown
,无法判断赋值语句是否合法,因为在面向对象语言中子类对象可以赋值给父类引用,但是父类对象不允许赋值给子类的引用。
Collection<?> c = new LinkedList<String>();
c.add(new Object()); // Compile time error
((LinkedList<String>)c).add(0, "hello world"); // success
上例中的集合 c,尽管使用了泛型的方式来声明和定义,但其本质仍然是 ArrayList
没有改变。如果想让 c 中添加元素,必须进行强制类型转换。
public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // Compile-time error!
}
上例中,为什么会产生编译错误?因为这里既然使用了通配符,就说明元素的真正类型是未知的,是 unknown
,如果传入的实参类型为 LinkedList
,那么调用 shapes.add() 时,必然会发生运行时错误,而通过泛型的方式,可以在编译期检测,来规避这种错误。
使用了通配符的集合,是不能编辑的,因为通配符没有提供关于类型的准确信息,如果既要使用泛型,又要编辑对象,怎么办呢?
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // Correct
}
}
extends T>
表示一切继承了 T 的 unknown
类型。
例如:
public void test(List<? extends Number> list){
...
}
这里的 ?
可以是 Integer, Long, Float ..
, 但肯定不能为 String
当方法中需要改变泛型对象的属性,或者返回特定的类型时,需要定义泛型方法,而不是使用通配符。
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // Correct
}
}
好好理解下面例子中的各种情况:
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();
// T inferred to be Object
fromArrayToCollection(oa, co);
String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();
// T inferred to be String
fromArrayToCollection(sa, cs);
// T inferred to be Object
fromArrayToCollection(sa, co);
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
// T inferred to be Number
fromArrayToCollection(ia, cn);
// T inferred to be Number
fromArrayToCollection(fa, cn);
// T inferred to be Number
fromArrayToCollection(na, cn);
// T inferred to be Object
fromArrayToCollection(na, co);
// compile-time error
fromArrayToCollection(na, cs);
泛型方法会做一定的类型推断。