List <Integer> myIntList = new LinkedList <Integer>(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = myIntList.iterator().next(); // 3
注意变量myIntList的类型声明。它指定这不是一个任意的List,而是一个Integer的List,写作:List <Integer>。我们说List是一个带一个类型参数的泛型接口(a generic interface that takes a type parameter),本例中,类型参数是Integer。我们在创建这个List对象的时候也指定了一个类型参数。
(原文:Type parameters can be used throughout the generic declaration, pretty much where you would use ordinary types (though there are some important restrictions; see section 7))
在介绍那一节我们看到了对泛型类型声明List(the generic type declaration List)的调用,如List <Integer>。在这个调用中(通常称作一个参数化类型a parameterized type),所有出现形式类型参数(formal type parameter,这里是E)都被替换成实体类型参数(actual type argument)(这里是Integer)。
类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数(formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用,实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。
public void drawAll(List <? extends Shape> shapes) { //..}
这里有一处很小但是很重要的不同:我们把类型 List <Shape> 替换成了 List <? extends Shape>。现在drawAll()可以接受任何Shape的子类的List,所以我们可以对List <Circle>进行调用。
List <? extends Shape>是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape的一个子类(它可以是Shape本身或者Shape的子类而不必是extends自Shape)。我们说Shape是这个通配符的上限(upper bound)。
注意,我们并没有传送真实类型参数(actual type argument)给一个泛型方法。编译器根据实参为我们推断类型参数的值。它通常推断出能使调用类型正确的最明确的类型参数(原文是:It will generally infer the most specific type argument that will make the call type-correct.)。
(原文:Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type. If there isn’t such a dependency, a generic method should not be used.)
一前一后的同时使用泛型方法和通配符也是可能的。下面是方法 Collections.copy():
class Collections {
public static <T> void copy(List <T> dest, List <? extends T> src){...}
(原文:As consequence, the static variables and methods of a class are also shared among all the instances. That is why it is illegal to refer to the type parameters of a type declaration in a static method or initializer, or in the declaration or initializer of a static variable.)
(原文:The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type.You can declare array types whose element type is a type variable or a parameterized type, but not array objects.)
这很烦人,但是确实时这样。为了避免下面的情况,必须有这样的限制:
List <String>[] lsa = new List <String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[]) o;
List <Integer> li = new ArrayList <Integer>();
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException
如果参数化类型可以是数组,那么意味着上面的例子可以没有任何unchecked warnings的通过编译,但是在运行时失败。我们把类型安全(type-safety)作为泛型首要的设计目标。特别的,java语言被设计为保证:如果你的整个程序没有unchecked warnings的使用javac –source1.5通过编译,那么它是类型安全的(原文: if your entire application has been compiled without unchecked warnings using javac -source 1.5, it is type safe)。
List <?>[] lsa = new List <?>[10]; // ok, array of unbounded wildcard type
Object o = lsa;
Object[] oa = (Object[]) o;
List <Integer> li = new ArrayList <Integer>();
li.add(new Integer(3));
oa[1] = li; // correct
String s = (String) lsa[1].get(0); // run time error, but cast is explicit
在下面的变体中,我们避免了产生一个元素类型是参数化的数组对象,但是使用了元素类型参数化的类型。(译注:意思如下面的第一行代码所示,声明一个泛型化的数组,但是new的时候使用的是raw type,原文中是 new ArrayList <?>(10),那是错的,已经修正为new ArrayList(10);)这是合法的,但是产生一个unchecked warning。实际上,这个代码是不安全的,最后产生一个错误。
List <String>[] lsa = new ArrayList[10]; // unchecked warning - this is unsafe!
Object o = lsa;
Object[] oa = (Object[]) o;
List <Integer> li = new ArrayList <Integer>();
li.add(new Integer(3));
oa[1] = li; // correct
String s = lsa[1].get(0); // run time error, but we were warned
类似的,创建一个元素类型是一个类型变量的数组对象导致一个编译时错误:
<T> T[] makeArray(T t) {
return new T[100]; // error
}
因为类型变量在运行时并不存在,所以没有办法决定实际类型是什么。
解决这些限制的办法是使用字面的类作为运行时类型标志(原文:use class literals as run time type tokens),见第8部分。
8. Class Literals as Run-time Type Tokens JDK1.5中一个变化是类 java.lang.Class是泛型化的。这是把泛型作为容器类之外的一个很有意思的例子(using genericity for something other than a container class)。
现在,Class有一个类型参数T, 你很可能会问,T 代表什么?
它代表Class对象代表的类型。比如说,String.class类型代表 Class <String>,Serializable.class代表 Class <Serializable>。着可以被用来提高你的反射代码的类型安全。
T 精确的(exactly)和自己能比较是不需要的。所需要的是 T能够和它的父类中的一个进行比较,这导出:(注:Collections.max()的实际方法签名更复杂,我们在第10部分再讨论。)
public static <T extends Comparable <? super T>> T max(Collection <T> coll)
这个推论对大多数想让 Comparable 对任意类型生效的用法中都有效:你总是应该使用 Comparable <? super T>。
总之,如果你有一个只使用类型参数T作为参数的API,它的使用应该利用下限通配符( ? super T )的好处。相反的,如果API只返回T,你应该使用上限通配符( ? extends T )来给你的客户端更大的灵活性。
(原文:This reasoning applies to almost any usage of Comparable that is intended to work for arbitrary types: You always want to use Comparable <? super T>.
In general, if you have an API that only uses a type parameter T as an argument, its uses should take advantage of lower bounded wildcards (? super T). Conversely, if the API only returns T, you'll give your clients more flexibility by using upper bounded wildcards (? extends T). )。
9.1. 通配符匹配(wildcard capture) 现在应该很清晰,如果给定:
Set <?> unknownSet = new HashSet <String>(); ...
/** 向 Set s 中添加一个元素*/
public static <T> void addToSet(Set <T> s, T t) {...}
(原文:Because this situation arises relatively frequently, there is a special rule that allows such code under very specific circumstances in which the code can be proven to be safe. This rule, known as wildcard capture, allows the compiler to infer the unknown type of a wildcard as a type argument to a generic method.)
你还应该保证修订过的API保持与老客户端的二进制兼容。者以为者API的erasure必须与老的未泛型化版本一样。在大多数情况下,这是很自然的结果,但是有些精巧的情形(subtle cases)。我们看看我们已经碰到过的精巧的情形中的一个(one of the subtle cases),方法Collections.max()。就像我们在第9部分看到的,一个似是而非的max()的方法签名是:
public static <T extends Comparable <? super T>> T max(Collection <T> coll)
(原文:This is an example of giving multiple bounds for a type parameter, using the syntax T1& T2 ... & Tn. A type variable with multiple bounds is known to be a subtype of all of the types listed in the bound. When a multiple bound is used, the first type mentioned in the bound is used as the erasure of the type variable.)
public static <T extends Object & Comparable <? super T>> T max(Collection <? extends T> coll)
实际中出现那么棘手的问题是很罕见的,但是专业库设计师应该准备好非常仔细的考虑转换现存的API。
另一个需要小心的问题是协变式返回值(covariant returns),就是说在子类中获得一个方法的返回值(refining the return type of a method in a subclass)。在老API中你无法使用这个特性带来的好处。
为了知其原因,让我们看一个例子。
假定你的原来的API是下面的形式:
public class Foo {
public Foo create(){...}
// Factory, should create an instance of whatever class it is declared in
}
public class Bar extends Foo {
public Foo create(){...} // actually creates a Bar
}
为了使用协变式返回值的好处,你把它改成:
public class Foo {
public Foo create(){...}
// Factory, should create an instance of whatever class it is declared in
}
public class Bar extends Foo {
public Bar create(){...} // actually creates a Bar
}
现在,假定你的一个第三方客户代码:
public class Baz extends Bar {
public Foo create(){...} // actually creates a Baz
}
Java虚拟机并不直接支持不同类型返回值的方法重载。这个特性是由编译器来支持的。因此,除非Baz类被重新编译,它不会正确的重载Bar的create()方法,而且,Baz必须被修改,因为Baz的代码被拒绝,它的create的返回值不是Bar中create返回值的子类。(原文: Consequently, unless the class Baz is recompiled, it will not properly override the create() method of Bar.Furthermore, Baz will have to be modified, since the code will be rejected as written - the return type of create() in Baz is not a subtype of the return type of create() in Bar.)
(译注:上面的一段话有些莫名其妙,我测试过这个例子,在jdk1.4下,三个类都编译之后改变Bar,只在jdk5下重新编译Bar,然后在jdk5下,Baz仍然能够被使用,当然那,无法使用 Baz b = baz.create();这样的代码。)
11. 致谢 Erik Ernst, Christian Plesner Hansen, Jeff Norton, Mads Torgersen, Peter von der Ah´e and Philip Wadler contributed material to this tutorial.
Thanks to David Biesack, Bruce Chapman, David Flanagan, Neal Gafter, ¨ Orjan Petersson,Scott Seligman, Yoshiki Shibata and Kresten Krab Thorup for valuable feedback on earlier versions of this tutorial. Apologies to anyone whom I’ve forgotten.