Generic 泛型 -- 类型的类型
第一个问题,什么是泛型
从字面上理解就是 类型的类型。泛型并不是 Java 独有的特性,事实上,Java的泛型在某种意义上是一种折中的实现,泛型的灵感来源于C++的模版类,但在实现的灵活性上又不同于C++的模版类。
考虑下面这种情况,当我们调用一个方法的时候,需要传入一个对象作为参数,实际调用的时候,我们传入的是这个参数的子类,这种情况是很常见的。
void method(Father father) {}
method(new Son());
Son 是 Father 类的子类。有时候 Son 可能已经继承了其他的父类,由于 Java 是单继承,Son 不能再继承Father 类,这种情况下可以考虑使用接口。
class Son extends OtherFather implements Father ()
method(new Son());
使用接口消除了单继承的局限性,然而,有时候即使使用接口也是有局限性的,因为我们仍然要给 method() 方法指定具体的接口类型,就是 Father.
有没有一种方法可以不指定具体的参数类型,而在需要时再指定?实势造泛型!
入门 -- 一个简单的泛型使用
容器类是泛型出现的原动力之一。See the following sample code:
List<String> list = new ArrayList<String>();
list.add("add string");
list.add(new Integer(1)); // Can not compile
上边的代码是编译不过的,可见,泛型在编译期就会检查传入的参数类型是否正确,也许这也是 JDK1.5推出泛型的原因之一吧。这种检查是合理的,如果把尖括号的内容去掉,就可以编译通过,但是,到运行时我们很可能会得到一个 CanNotCastException,容器大多时候只用来保存同一类型的对象。既然我们可以在编译期就能避免这样的错误,何苦而不为呢。
List<String> 的意思是,一个保存String对象的List(不知道这种说法是否正确)。甚至可以简单地直接用一个字母<T> 代替 <String>, List<T>的意思是,一个保存类型为 T 的对象的List.
List<T> list = new ArrayList<T>();
泛型不仅可以用于容器类,还可以用于一般类,方法,接口,内部类等。以下的声明都是正确的。
class Tuble<A, B, C> {}
public interface Interface<T> {}
abstract class Generator<T> {
T temp;
T gen() { return temp; }
}
// P.484 The mystery of erease -- bounds, sel-bounding,wildcard, arrays of generic, issue of generic
Erease -- 神秘的“消磁”
Eease 的直译意思是擦去,除去,有人翻译为“消磁”。
Class clz = List<Stirng>.class; // Can not compile
上面的代码是编译不过的。因为 (new ArrayList<String>).getCLass() 与 (new ArrayList<Integer>).getCLass() 是相等的。在知道 List<T> 的类型仍是 List.class. 这就是 erease, 就是在运行时,类型T好象是被“抹去”了。
class EreaseTest {
void fun() { System.out.print("fun()"); }
}
class Erease<T> {
private T obj;
Erease(T type) {
this.obj = type;
}
void invoke() { obj.fun(); } // Can not compile
}
上面的代码明显编译不过的,因为在知道 T 的具体类型之前就调用 obj.fun() 是不对的,编译器并不知道 T 中注册了fun() 方法,那么怎么让编译器知道 T 中注册了fun() 方法呢?
class Erease<T extends EreaseTest> {
private T obj;
Erease(T type) {
this.obj = type;
}
void invoke() { obj.fun(); } // Can not compile
}
用 extends 关键字给泛型指定一个边界,编译器就足够聪明可以知道 T 中注册了哪些方法。extends 并不是继承的意思,Erease<T extends EreaseTest> 意思是指,类型参数 T 可以是 Erease类或者其任何子类。
这一点上与 C++ 的模版类的实现不同,C++中是允许在知道 T 的具体类型前对 T 进行方法调用的。Java是在1.5之后才有泛型的,可能是也要考虑与1.4之前的版本兼容,泛型只用于编译期的静态类型检查,运行时,所有关于泛型的信息会被“除去”,也就是erease.
在指定边界的时候可以使用通配符 ?, List<? extends Erease> 的意思是,一个任何 Erease 子类的List, 他与 “一个保存任何Erease子类的List” 是完全不一样的。
可以把边界指定为自身,例如
class SelfBound<T extends SelfBound> {}
如果子类在父类参数类型中出现,还有一种叫法是CRG-- Curiously Recurring Generic.
class Father<T> {}
class Son extends Father<Son> {}
这是相当有意义的,可以使得父类 Father 成为子类Son 的一个模版。