Generics in the Java Programming Language
Java编程语言中的泛型
Gilad Bracha
July 5, 2004
Contents内容
1 Introduction 介绍2
2 Defining Simple Generics定义简单泛型 3
3 Generics and Subtyping 泛型和子类型4
4 Wildcards 通配符5
4.1 Bounded Wildcards 具有边界的通配符 . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
5 Generic Methods 泛型方法7
6 Interoperating with Legacy Code 和遗留代码的互操作 10
6.1 Using Legacy Code in Generic Code 在泛型代码中使用遗留代码. . . . . . . . . . . . . . . . . . 10
6.2 Erasure and Translation 擦出和翻译. . . . . . . . . . . . . . . . . . . . . . . . . 12
6.3 Using Generic Code in Legacy Code 在遗留代码中使用泛型代码. . . . . . . . . . . . . . . . . . 13
7 The Fine Print 好的打印14
7.1 A Generic Class is Shared by all its Invocations泛型类型被所有它的调用共享 . . . . . . . . . . . . 14
7.2 Casts and InstanceOf 转型和Instanceof操作符. . . . . . . . . . . . . . . . . . . . . . . . . . 14
7.3 Arrays 数组. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
8 Class Literals as Run-time Type Tokens 类字面量作为运行时类型符号16
9 More Fun with Wildcards 更有趣的通配符18
9.1 Wildcard Capture 通配符捕获. . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
10 Converting Legacy Code to Use Generics 将遗留代码转换为使用泛型20
11 Acknowledgements 声明23
1
1 Introduction介绍
JDK 1.5 introduces several extensions to the Java programming language.JDK1.5引入了Java编程语言的扩展。 One of these is the introduction of generics.其中一个就是泛型的引入。
This tutorial is aimed at introducing you to generics. 这些教程的目的就是给您介绍泛型。You may be familiar with similar constructs from other languages, most notably C++ templates.你可能从其他语言中熟悉相似的结构,最著名的如C++模板。 If so, you’ll soon see that there are both similarities and important differences.如果是这样,你不久将看到他们之间相似的地方和重要不同的地方。 If you are not familiar with look-a-alike constructs from elsewhere, all the better; you can start afresh, without unlearning any misconceptions.如果你不熟悉其他的看起来比较像的结构,这样也好,你可以从新的开始学习,而不会被某些错误的概念混淆。
Generics allow you to abstract over types. 泛型允许你抽象所有的类型。 The most common examples are container types, such as those in the Collection hierarchy. 最普通的例子是容器类型,比如Collection继承结果中的这些。
Here is a typical usage of that sort:下面就是这种的典型用法:
List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3
The cast on line 3 is slightly annoying. 第三行比较让人烦。Typically, the programmer knows what kind of data has been placed into a particular list.典型的是,程序员知道对于特定的list中是哪种数据。 However, the cast is essential.然而,转型是必须的。 The compiler can only guarantee that an Object will be returned by the iterator. 编译器仅能保证通过iterator返回的是一个Object。To ensure the assignment to a variable of type Integer is type safe, the cast is required.为了确保赋值到一个Interger类型的变量是类型安全的,转型是必须的。
Of course, the cast not only introduces clutter. 当然,转型不仅仅会引入混乱。It also introduces the possibility of a run time error, since the programmer might be mistaken.它也引入了运行时错误的可能性,因为程序员可能被误导。What if programmers could actually express their intent, and mark a list as being restricted to contain a particular data type?如果程序员能够实际表达他们的意图是什么,并且标记一个list作为限制到包含一个特定的数据类型? This is the core idea behind generics.这就是泛型背后的核心思想。
Here is a version of the program fragment given above using generics:下面是上述例子中使用泛型的版本:
List<Integer> myIntList = new LinkedList<Integer>(); // 1’
myIntList.add(new Integer(0)); //2’
Integer x = myIntList.iterator().next(); // 3’
Notice the type declaration for the variable myIntList. 注意变量myIntList的类型声明。It specifies that this is not just an arbitrary List, but a List of Integer, written List<Integer>. 它明确了这个不是一个任意的List,而是一个Integer类型的List,写为List<Integer>。We say that List is a generic interface that takes a type parameter - in this case, Integer.我们说,这种情况下是,List是使用类型参数的泛型接口。 We also specify a type parameter when creating the list object.我们在创建list对象的时候也制定了类型参数。
The other thing to pay attention to is that the cast is gone on line 3’在第3行,我们需要另外的一件事情就是转型。.
Now, you might think that all we’ve accomplished is to move the clutter around.现在,我们可能认为我们已经成功完成的一起就是把混乱走。Instead of a cast to Integer on line 3, we have Integer as a type parameter on line 1’.在第3行不再是转型Integer,我们得到了在第1行的类型参数Integer。However, there is a very big difference here. 然而,这里有一个大的区别。The compiler can now check the type correctness of the program at compile-time. 编译器现在能够在编译时检查程序的类型正确性。When we say that myIntList is declared with type List<Integer>, this tells us something about the variable myIntList, which holds true wherever and whenever it is used, and the compiler will guarantee it.当我们说myIntList使用类型List<Integer> 声明的时候,这就告诉我们关于变量myIntList的一些事情,就是无论在什么地方、什么时候被使用的时候,编译器将确保这一点。In contrast, the cast tells us something the programmer thinks is true at a single point in the code.作为比较,转型告诉我们的一件事情就是程序员在代码中的单个点上的想法是正确的。
The net effect, especially in large programs, is improved readability and robustness.网络的影响,特别是在大型的程序中,这提高了程序的可读性和健壮性。
2 Defining Simple Generics 定义简单泛型
Here is a small excerpt from the definitions of the interfaces List and Iterator in package
java.util:在包java.util中,从接口List和Interator的定义上有小小的过度使用:
public interface List<E> { void add(E x);
Iterator<E> iterator();
}public interface Iterator<E> { E next();
boolean hasNext();
}
This should all be familiar, except for the stuff in angle brackets.这看起来比较熟悉,出国三角括号中的元素。 Those are the declarations of the formal type parameters of the interfaces List and Iterator.这些就是接口List和Iterator的形式类型参数的声明。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).类型参数在泛型声明中被通篇使用,景观你还是使用普通的类型(尽管有些限制;参考第7部分)。
In the introduction, we saw invocations of the generic type declaration List, such as List<Integer>.在这个例子中,我们看到泛型类型声明List的调用,例如List<Integer>。 In the invocation (usually called a parameterized type), all occurrences of the formal type parameter (E in this case) are replaced by the actual type argument (in this case, Integer).在这个调用中(通常被称之为参数化类型),所有的形式类型参数的出现(在这个例子中是E)被实际参数(在这个例子中是Integer)所替换。
You might imagine that List<Integer> stands for a version of List where E has been uniformly replaced by Integer:你可能想象List<Integer>代表E已经被Integer统一替换的版本:
public interface IntegerList { void add(Integer x)
Iterator<Integer> iterator();
}
This intuition can be helpful, but it’s also misleading.这种直觉有助于理解,但是它还是一种误解。
It is helpful, because the parameterized type List<Integer> does indeed have methods that look just like this expansion.它是有用的,因为参数类型List<Integer>确实具有看起来像扩展的方法。
It is misleading, because the declaration of a generic is never actually expanded in this way.这是一种误解,因为泛型从不会以这种扩展的方法来声明。 There aren’t multiple copies of the code: not in source, not in binary, not on disk and not in memory. 没有这种代码的多分拷贝:源代码不是、二进制不是、磁盘上不是并且内存中也不是。If you are a C++ programmer, you’ll understand that this is very different than a C++ template.如果您是一个C++程序员,你将会立即这是和C++模板非常不同的一点。
A generic type declaration is compiled once and for all, and turned into a single class file, just like an ordinary class or interface declaration.泛型类型声明之编译一次,并且形成单个class文件,就像一个普通类或者接口声明。
Type parameters are analogous to the ordinary parameters used in methods or constructors.类型参数和在方法或者构造器中使用的普通参数相似。 Much like a method has formal value parameters that describe the kinds of values it operates on, a generic declaration has formal type parameters. 更像一个方法具有形式值参数,描述了它操作的值得类型,泛型声明具有形式类型参数。When a method is invoked, actual arguments are substituted for the formal parameters, and the method body is evaluated.当一个方法被调用,形式参数被实际参数替换。 When a generic declaration is invoked, the actual type arguments are substituted for the formal type parameters.当一个泛型声明被调用,实际类型参数替换形式类型参数。
A note on naming conventions. 注意命名转换。We recommend that you use pithy (single character if possible) yet evocative names for formal type parameters. 我们推荐你使用精炼的也易于称呼的名称(如果可能,就是用单个字符)来作为形式类型参数。It’s best to avoid lower case characters in those names, making it easy to distinguish formal type parameters from ordinary classes and interfaces.最好避免在这些名字中使用小写字符。 Many container types use E, for element, as in the examples above. 许多容器类型使用E来描述元素,就像在例子中那样。We’ll see some additional conventions in later examples. 在后续的例子中,我们将看到附加的转换。
3 Generics and Subtyping泛型和子类型
Let’s test our understanding of generics. 让我们测试一下我们对泛型的理解。Is the following code snippet legal?下面的代码是否合法?
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2
Line 1 is certainly legal. 第1行确实是合法的。The trickier part of the question is line 2.问题的中绕人的地方是第2行。 This boils down to the question: is a List of String a List of Object. 这个问题归结为:String类型的List和Object类型的List。 Most people’s instinct is to answer: “sure!”. 大部分人本能认为答案是“确定的”。
Well, take a look at the next few lines:好吧,让我们看下面的新行:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: attempts to assign an Object to a String!
Here we’ve aliased ls and lo. Accessing ls, a list of String, through the alias lo, we can insert arbitrary objects into it.这里,我们已经给ls和lo起了别名。 As a result ls does not hold just Strings anymore, and when we try and get something out of it, we get a rude surprise.因此ls不再仅仅持有String类型,并且当我们试图从它里面得到某些东西的时候,我们会得到令人惊讶的结果。
The Java compiler will prevent this from happening of course.Java编译器将组织这种情况发生。 Line 2 will cause a compile time error.第2行会产生一个编译时错误。In general, if Foo is a subtype (subclass or subinterface) of Bar, and G is some generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>. 通常,如果Foo是Bar的子类型(子类或者子接口),并且G是某个泛型类型声明,那么在这种情况下,G<Foo>是G<Bar>的子类型。This is probably the hardest thing you need to learn about generics, because it goes against our deeply held intuitions.对于学习泛型的人来说,这是最难以理解的地方,因为这和我们已有的直觉想抵触。
The problem with that intuition is that it assumes that collections don’t change.具有这种直觉的问题是,假定这个集合没有改动。Our instinct takes these things to be immutable.我们本能认为这些是不能改变的。
For example, if the department of motor vehicles supplies a list of drivers to the census bureau, this seems reasonable.举个例子,如果摩托车的部门提供了一组驾驶者给检查部门,这看起来比较合理。 We think that a List<Driver> is a List<Person>, assuming that Driver is a subtype of Person. 我们认为List<Driver>是List<Person>,假设Driver是Person的子类型。In fact, what is being passed is a copy of the registry of drivers. 实际上,被传递的是驾驶者的登记的一个拷贝。Otherwise, the census bureau could add new people who are not drivers into the list, corrupting the DMV’s records.否则,检查部门可以增加不是驾驶者的新的人到这个列表中,损坏了DMV记录。
In order to cope with this sort of situation, it’s useful to consider more flexible generic types.为了处理这种情况,处理更灵活的泛型类型是有用处的。The rules we’ve seen so far are quite restrictive.我们看到的这个规则是比较严格的。
4 Wildcards通配符
Consider the problem of writing a routine that prints out all the elements in a collection. 考虑写一个打印出一个集合中所有元素的例子。Here’s how you might write it in an older version of the language:这里有你如何在就的语言版本中的写法:
void printCollection(Collection c) { Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) { System.out.println(i.next());
}}
And here is a naive attempt at writing it using generics (and the new for loop syntax):并且,这里还有一个很幼稚的使用泛型(并且新的for语义支持)的例子:
void printCollection(Collection<Object> c) { for (Object e : c) { System.out.println(e);
}}
The problem is that this new version is much less useful than the old one. 问题是这个新的版本并不比旧的更有用。Whereas the old code could be called with any kind of collection as a parameter, the new code only takes Collection<Object>, which, as we’ve just demonstrated, is not a supertype
of all kinds of collections!尽管老的代码能够和以任何种类的集合作为参数被调用,新的代码仅仅可以使用Coollection<Object>,这样,正像我们已经演示的那样,并非所有集合类型的超类型。
So what is the supertype of all kinds of collections? 因此,所有集合的超类型是什么呢?It’s written Collection<?> (pronounced “collection of unknown”) , that is, a collection whose element type matches anything. It’s called a wildcard type for obvious reasons. 它被写成Collection<?>(发音为“未知的集合”)We can write:我们可以这样写:
void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e);
}}
and now, we can call it with any type of collection. 现在,我们可以使用任何种类的集合来调用它。Notice that inside printCollection(), we can still read elements from c and give them type Object. 注意内部的printCollection(),我们能从c中读取元素并且他们的类型都是Object。This is always safe, since whatever the actual type of the collection, it does contain objects. 这通常是安全的,因为无论集合的实际类型为什么,它都包含对象。It isn’t safe to add arbitrary objects to it however:可以,给他增加任意的对象却是不安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // compile time error
Since we don’t know what the element type of c stands for, we cannot add objects to it. 因为我们不知道c的元素类型代表了什么,我们不能给它增加对象。The add() method takes arguments of type E, the element type of the collection. 方法add()具有类型E的参数,集合的元素类型。 When the actual type parameter is ?, it stands for some unknown type.实际的类型参数是什么?,它代表了某个未知类型。 Any parameter we pass to add would have to be a subtype of this unknown type.任何我们传递给add的参数都不得不是这个未知类型的子类型。 Since we don’t know what type that is, we cannot pass anything in.因为我们不知道类型是什么,我们不能传递任何东西给他。 The sole exception is null, which is a member of every type.唯一的异常时null,它是任何类型的一个成员。
On the other hand, given a List<?>, we can call get() and make use of the result. 另一方面,给定一个List<?>,我们可以调用get(),并且可以使用返回值。The result type is an unknown type, but we always know that it is an object. 返回值类型是未知类型,但是我们知道它是一个对象。It is therefore safe to assign the result of get() to a variable of type Object or pass it as a parameter where the type Object is expected.因此,将get()的结果赋值给Object类型的变量,或者将它在Object类型出现的时候作为一个参数。
4.1 Bounded Wildcards边界通配符
Consider a simple drawing application that can draw shapes such as rectangles and circles.考虑一个简单的画图应用程序,可以画圆或者方的形状。 To represent these shapes within the program, you could define a class hierarchy such as this:为了在程序中表示这些形状,你可以定义一个如下的类层次:
public abstract class Shape { public abstract void draw(Canvas c);
}public class Circle extends Shape { private int x, y, radius;
public void draw(Canvas c) { ... }
}public class Rectangle extends Shape { private int x, y, width, height;
public void draw(Canvas c) { ... }
}
These classes can be drawn on a canvas:这些类可以在一个画布上画出来:
public class Canvas { public void draw(Shape s) { s.draw(this);
}
}
Any drawing will typically contain a number of shapes.任何图画都会典型的包含形状的数字。 Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:假设他们被表示为一个类型,在画他们的画布中使用一个方法是比较方便的:
public void drawAll(List<Shape> shapes) { for (Shape s: shapes) { s.draw(this);
}
}
Now, the type rules say that drawAll() can only be called on lists of exactly Shape: it cannot, for instance, be called on a List<Circle>. 现在,类型规则认为drawAll()仅能调用列表中的Shape:这是不行的,举个例子,在List<Circle>上调用。That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on a List<Circle>.这是不幸的,因为所有的方法所作的就是从列表中读取形状,因此它仅仅只要在List<Circle>上调用。What we really want is for the method to accept a list of any kind of shape: 我们真正需要的是对于这个方法接受任何种类形状的列表:
public void drawAll(List<? extends Shape> shapes) { ... }
There is a small but very important difference here: 这是很小的但是确实非常重要的差别:we have replaced the type List<Shape> with List<? extends Shape>. 我们已经使用List<?extends Shape>替换类型List<Shap>。Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List<Circle> if we want.现在drawAll()将接受Shape任何子类的列表,因此,我们现在如果我们需要就能在List<Circle>上调用。
List<? extends Shape> is an example of a bounded wildcard. List<?extends Shape>是边界通配符的例子。The ? stands for an unknown type, just like the wildcards we saw earlier.?代表一种未知类型,就像我们早期看到的通配符。However, in this case, we know that this unknown type is in fact a subtype of Shape1.然而,在这种情况下,我们知道未知类型实际上是Shape的子类型。 We say that Shape is the upper bound of the wildcard.我们说Shape是通配符的上界。
There is, as usual, a price to be paid for the flexibility of using wildcards. 这里,和通常一样,使用通配符的灵活性是需要付出代价的。That price is that it is now illegal to write into shapes in the body of the method. 这个代价是在方法体重写入shapes时非法的。For instance, this is not allowed:举个例子,这是不允许的:
public void addRectangle(List<? extends Shape> shapes) { shapes.add(0, new Rectangle()); // compile-time error!
}
You should be able to figure out why the code above is disallowed. 你应该考虑上面的代码是不被允许的。The type of the second parameter to shapes.add() is ? extends Shape - an unknown subtype of Shape. Shapes.add()的第二个参数的类型是? extends Shape –Shape的未知子类型。Since we don’t know what type it is, we don’t know if it is a supertype of Rectangle; it might or might not be such a supertype, so it isn’t safe to pass a Rectangle there.因为我们不知道他是什么类型,我们不知道它是否是Rectangle的超类型;它可能是也可能不是这种超类型,因为这里传递Rectangle是不安全的。
Bounded wildcards are just what one needs to handle the example of the DMV passing its data to the census bureau.边界通配符仅仅是一个人需要处理DMV传递它的数据给检查部门的例子。 Our example assumes that the data is represented by mapping from names (represented as strings) to people (represented by reference types such as Person or its subtypes, such as Driver). 我们的例子假定,这些数据通过从名称(表示为字符串)到人(通过引用类型,比如Person或者它的子类型,比如Driver来表示)来表示。Map<K,V> is an example of a generic type that takes two type arguments, representing the keys and values of the map. Map<K,V>是具有两个类型参数的泛型类型的例子,表示这个map的关键字和值。
Again, note the naming convention for formal type parameters - K for keys and V for values.在一次,注意形式类型参数的命名转换-K为关键字,V为值。
public class Census { public static void
addRegistry(Map<String, ? extends Person> registry) { ...}
}...
Map<String, Driver> allDrivers = ...;
Census.addRegistry(allDrivers);
5 Generic Methods泛型方法
Consider writing a method that takes an array of objects and a collection and puts all objects in the array into the collection.考虑写一个具有对象数组和集合以及将所有数组放入这个集合的方法。
Here is a first attempt:这是第一次尝试:
static void fromArrayToCollection(Object[] a, Collection<?> c) { for (Object o : a) { c.add(o); // compile time error
}}
By now, you will have learned to avoid the beginner’s mistake of trying to use Collection<Object> as the type of the collection parameter. 到现在为止,你已经完全学会了避免初学者试图使用Collection<Object>作为集合参数类型的错误。You may or may not 1(It could be Shape itself, or some subclass; it need not literally extend Shape.)have recognized that using Collection<?> isn’t going to work either.你可能或者没有(它可能是Shape本身,或者某个子类;它不需要字面扩展Shape)已经认识到使用Collection<?>。 Recall that you cannot just shove objects into a collection of unknown type.回忆你不能仅仅把对象推入未知类型的集合中。
The way to do deal with these problems is to use generic methods. 处理这种问题的方法是使用泛型方法。Just like type declarations, method declarations can be generic - that is, parameterized by one or more type parameters.就像类型声明,方法声明可以是泛型的-就是说,通过一个或者更多的类型参数参数化。
static <T> void fromArrayToCollection(T[] a, Collection<T> c) { for (T o : a) { c.add(o); // correct
}}
We can call this method with any kind of collection whose element type is a supertype of the element type of the array.我们可以使用其元素类型是数组的元素类型的一个子类型的任何集合调用这个方法。
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(oa, co);// T inferred to be Object T引用是Object
String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();
fromArrayToCollection(sa, cs);// T inferred to be String T引用是String
fromArrayToCollection(sa, co);// T inferred to be Object T引用是Object
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn);// T inferred to be Number T引用是Number
fromArrayToCollection(fa, cn);// T inferred to be Number T引用是Number
fromArrayToCollection(na, cn);// T inferred to be Number T引用是Number
fromArrayToCollection(na, co);// T inferred to be Object T引用是Object
fromArrayToCollection(na, cs);// compile-time error 编译时错误
Notice that we don’t have to pass an actual type argument to a generic method. 注意我们没有不得不将一个实际类型参数传递给泛型方法。The compiler infers the type argument for us, based on the types of the actual arguments. 编译器基于实际参数类型为我们推断类型参数。It will generally infer the most specific type argument that will make the call type-correct. 它通常推断是的调用类型正确的大部分特定类型参数。One question that arises is: when should I use generic methods, and when should I use wildcard types? 一个问题出现了:我什么时候使用泛型方法,并且什么时候我应该使用通配符类型。To understand the answer, let’s examine a few methods from the Collection libraries.为了理解这个问题,让我们检查集合库中的几个方法:
interface Collection<E> { public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
We could have used generic methods here instead:我们使用泛型方法在这里替换:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c);
public <T extends E> boolean addAll(Collection<T> c);
// hey, type variables can have bounds too!嘿,类型变量也可以具有边界!
}
However, in both containsAll and addAll, the type parameter T is used only once. 然而,在containsAll和addAll中,类型参数T仅仅被使用一次。The return type doesn’t depend on the type parameter, nor does any other argument to the method (in this case, there simply is only one argument).返回类型也不依赖于类型参数,没有任何其他参数(在这个例子中,仅仅只有一个参数)。 This tells us that the type argument is being used for polymorphism; its only effect is to allow a variety of actual argument types to be used at different invocation sites.这些告诉我们,类型参数是被用来实现多态的;它的唯一的作用是允许在不同调用点使用多种实际参数类型。If that is the case, one should use wildcards. 如果是这种情况,应该使用通配符。 Wildcards are designed to support flexible subtyping, which is what we’re trying to express here.通配符被设计支持灵活子类型,这就是我们需要在这里阐述的内容。
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.如果这里没有这种依赖,泛型方法不会被使用。
It is possible to use both generic methods and wildcards in tandem. 一前一后的使用泛型方法和通配符是可能的。Here is the method Collections.copy():这里的方法是Collections.copy():
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src){...}
}
Note the dependency between the types of the two parameters. 注意两个参数类型直接的依赖性。Any object copied from the source list, src, must be assignable to the element type T of the destination list, dst. 任何从源列表,src中拷贝的对象,必须对目的列表中,dst,的T元素类型是可赋值的。So the element type of src can be any subtype of T - we don’t care which. 因此,src的元素类型可以是T的任何子类-我们不关心是哪一个。The signature of copy expresses the dependency using a type parameter, but uses a wildcard for the element type of the second parameter. copy的署名表达了使用类型参数的依赖性,但是在第二个参数的元素类型使用通配符。
We could have written the signature for this method another way, without using wildcards at all:我们已经为这个方法使用另外一个方式写书面,完全不需要通配符:
class Collections { public static <T, S extends T>
void copy(List<T> dest, List<S> src){...}
}
This is fine, but while the first type parameter is used both in the type of dst and in the bound of the second type parameter, S, S itself is only used once, in the type of src - nothing else depends on it.这是可以的,但是当地一个类型参数被同事在dst和第二个类型参数的边界中使用,S,S本身仅仅使用一次,在src的类型中-没有任何东西依赖它。 This is a sign that we can replace S with a wildcard. 这是使用通配符替换S的标识。Using wildcards is clearer and more concise than declaring explicit type parameters, and should therefore be preferred whenever possible.使用通配符比显示的声明类型参数更加清晰和简洁,并且在任何可能的时候都是最好这样做的。
Wildcards also have the advantage that they can be used outside of method signatures, as the types of fields, local variables and arrays. 通配符也具有优点,他们能够在方法署名之外使用,作为域、本地变量和数组的类型。Here is an example.这里有一个例子
Returning to our shape drawing problem, suppose we want to keep a history of drawing requests. 返回到我们的形状画图问题,假设我们想保留画图需求的历史。We can maintain the history in a static variable inside class Shape, and have drawAll() store its incoming argument into the history field.我们可以把历史保持在类Shape中的一个静态变量中,并且使用drawAll()存储传入到history域的参数。
static List<List<? extends Shape>> history =
new ArrayList<List<? extends Shape>>();
public void drawAll(List<? extends Shape> shapes) { history.addLast(shapes);
for (Shape s: shapes) { s.draw(this);
}}
Finally, again let’s take note of the naming convention used for the type parameters. 最后,我们重新注意使用类型参数的命名转换。We use T for type, whenever there isn’t anything more specific about the type to distinguish it. 我们使用T来表示类型,任何时候没有任何东西可以更特定的关于这个类型来区分它。This is often the case in generic methods.这就是在泛型方法中的这种情况。 If there are multiple type parameters, we might use letters that neighbor T in the alphabet, such as S. 如果有多个类型参数,我们可能使用T在字母表中的临近字母S。If a generic method appears inside a generic class, it’s a good idea to avoid using the same names for the type parameters of the method and class, to avoid confusion. 如果泛型方法出现在泛型类内部,这是一个避免使用相同方法和类的类型参数的好方法,这是为了避免让人疑惑。The same applies to nested generic classes.对于嵌套类也是同样的。
6 Interoperating with Legacy Code和遗留代码的互操作
Until now, all our examples have assumed an idealized world, where everyone is using the latest version of the Java programming language, which supports generics.到现在为止,我们所有的例子都是假定在理想世界,这里,每一个人都使用Java编程语言的最新版本,他们支持泛型。
Alas, in reality this isn’t the case. 唉,在现实中,不是这种情况。 Millions of lines of code have been written in earlier versions of the language, and they won’t all be converted overnight.成百万行的代码已经使用语言的早期版本写的,并且他们不可能一晚上全部转过来的。
Later, in section 10, we will tackle the problem of converting your old code to use generics.后面,在第10部分,我们将处理将旧代码转换为泛型的问题。In this section, we’ll focus on a simpler problem: how can legacy code and generic code interoperate? 在这部分,我们将聚焦于更简单的问题:遗留代码和泛型代码如何互操作。This question has two parts: using legacy code from within generic code, and using generic code within legacy code.这个问题具有两部分:在泛型代码中使用遗留代码,并且在遗留代码中使用泛型代码。
6.1 Using Legacy Code in Generic Code 在泛型代码中使用遗留代码
How can you use old code, while still enjoying the benefits of generics in your own code?你如何使用老的代码,同时在你的代码中还享受泛型的优点。As an example, assume you want to use the package com.Fooblibar.widgets. 作为一个例子,假设你想使用包com.Fooblibar.widgets 。The folks at Fooblibar.com 2 market a system for inventory control, highlights of which are shown below:这个文件夹在Fooblibar.com中,处理系统目录控制,下面的高亮部分:
package com.Fooblibar.widgets;
public interface Part { ...} public class Inventory { /**
* Adds a new Assembly to the inventory database.
* The assembly is given the name name, and consists of a set
* parts specified by parts. All elements of the collection parts
* must support the Part interface.
**/
public static void addAssembly(String name, Collection parts) {...} public static Assembly getAssembly(String name) {...}
}public interface Assembly { Collection getParts(); // Returns a collection of Parts
}
Now, you’d like to add new code that uses the API above.现在,你最好增加上面使用API的新代码。 It would be nice to ensure that you always called addAssembly() with the proper arguments - that is, that (2Fooblibar.com is a purely fictional company, used for illustration purposes. Any relation to any real company or institution, or any persons living or dead, is purely coincidental.)the collection you pass in is indeed a Collection of Part. 对于确保你总是使用正确参数调用addAssembly()-那就是,你传入的集合实际上是Part的集合。Of course, generics are tailor made for this:当然,泛型可以这些来做裁剪:
package com.mycompany.inventory;
import com.Fooblibar.widgets.*;
p...ublic class Blade implements Part {
}public class Guillotine implements Part {
}public class Main { public static void main(String[] args) { Collection<Part> c = new ArrayList<Part>();
c.add(new Guillotine()) ;
c.add(new Blade());
Inventory.addAssembly(”thingee”, c);
Collection<Part> k = Inventory.getAssembly(”thingee”).getParts();
}}
When we call addAssembly, it expects the second parameter to be of type Collection. 当我们调用addAssembly的时候,它期望第二个参数是Collection的类型。The actual argument is of type Collection<Part>. 实际参数是Collection<Part>类型。This works, but why? 这是可以工作的。After all, most collections don’t contain Part objects, and so in general, the compiler has no way of knowing what kind of collection the type Collection refers to.毕竟,绝大部分集合不包含Part对象,并且通常,编译器没有办法知道类型Collection引用的类型的种类。
In proper generic code, Collection would always be accompanied by a type parameter. 在正确的泛型代码中,Collection通常需要和一个类型参数配合。When a generic type like Collection is used without a type parameter, it’s called a raw type.当像Collection的泛型类型不使用类型参数来使用的时候,它被称之为原始类型。
Most people’s first instinct is that Collection really means Collection<Object>. 大部分人第一本能反应是Collection真正意味着Collection<Object>。However, as we saw earlier, it isn’t safe to pass a Collection<Part> in a place where a Collection<Object> is required. 然而,正如我们以前看到的,在需要Collection<Object>的地方传递一个Collection<Part>是不安全的。It’s more accurate to say that the type Collection denotes a collection of some unknown type, just like Collection<?>.更确切的说是类型Collection赋值了某个未知类型的集合,就像Collection<?>。
But wait, that can’t be right either! 但是等等,这也不对。Consider the call to getParts(), which returns a Collection. 考虑调用getparts(),它返回一个Collection。This is then assigned to k, which is a Collection<Part>. 被赋值给k,k是一个Collection<Part>类型。If the result of the call is a Collection<?>, the assignment would be an error.如果调用的结果是Collection<?>,这个赋值会产生一个错误。
In reality, the assignment is legal, but it generates an unchecked warning. 在现实中,这个赋值是合法的,但是它产生一个非检查警告。The warning is needed, because the fact is that the compiler can’t guarantee its correctness. 这个警告是需要的,因为实际上编译器不能确保其正确性。We have no way of checking the legacy code in getAssembly() to ensure that indeed the collection being returned is a collection of Parts.我们没有方法检查getAssembly()中的遗留代码来确保实际被返回的集合是Parts的集合。 The type used in the code is Collection, and one could legally insert all kinds of objects into such a collection.在代码中使用的类型是Collection,并且可以合法的将所有类型对象插入到这种集合中。
So, shouldn’t this be an error? 因此,这会是一个错误吗?Theoretically speaking, yes; but practically speaking, if generic code is going to call legacy code, this has to be allowed. 理论上说,是的;但是实际上说,如果泛型代码要调用遗留代码,这不得不这样被允许。It’s up to you, the programmer, to satisfy yourself that in this case, the assignment is safe because the contract of getAssembly() says it returns a collection of Parts, even though the type signature doesn’t show this.对你来说,程序员,为了在这种情况下让你自己满意,这个赋值时安全的,因为getAssembly()的合约说它返回了Part的集合,纵使类型数码并没有表现出这一点。
So raw types are very much like wildcard types, but they are not type checked as stringently.因此,原始类型非常像通配符类型,但是他们并不是那么严格的类型检查。 This is a deliberate design decision, to allow generics to interoperate with pre-existing legacy code.这是设计上故意的决定,为了允许泛型和以存在遗留代码的互操作。
Calling legacy code from generic code is inherently dangerous; once you mix generic code with non-generic legacy code, all the safety guarantees that the generic type system usually provides are void.从泛型代码中调用遗留代码是继承危险的;一旦你将泛型代码和非泛型代码混到一块,所有的泛型类型系统通常提供的安全保障都会无效。However, you are still better off than you were without using generics at all. At least you know the code on your end is consistent.然而,你还是比没有使用泛型会得到好处。至少你知道代码到你这边是持续的。
At the moment there’s a lot more non-generic code out there then there is generic code, and there will inevitably be situations where they have to mix.在这个时候,许多非泛型在泛型代码之外,在这种不可避免的情况下,他们不得不混合。
If you find that you must intermix legacy and generic code, pay close attention to the unchecked warnings. 如果你发现,你必须混合遗留和泛型代码,情紧密关注非检查警告。Think carefully how you can justify the safety of the code that gives rise to the warning.仔细考虑你如何满足发出告警的代码的安全性。
What happens if you still made a mistake, and the code that caused a warning is indeed not type safe? 如果你还是犯错误,会发生什么?并且导致警告的代码确实不类型安全?Let’s take a look at such a situation. 让我们看看这种情况。In the process, we’ll get some insight into the workings of the compiler. 在这个过程中,我们将获得编译器如何工作。
6.2 Erasure and Translation擦除和翻译
public String loophole(Integer x) { List<String> ys = new LinkedList<String>();
List xs = ys;
xs.add(x); // compile-time unchecked warning
return ys.iterator().next();
}
Here, we’ve aliased a list of strings and a plain old list.这里,我们重命名了字符串列表和老的列表。 We insert an Integer into the list, and attempt to extract a String. 我们给这个列表中插入一个Integer,并且试图解出一个String。This is clearly wrong. 这是明显错误的。If we ignore the warning and try to execute this code, it will fail exactly at the point where we try to use the wrong type.如果我们忽略这个警告,并且试图执行这些代码,在这个我们试图使用错误类型的点上失败。
At run time, this code behaves like:在运行时,这些代码的行为如下:
public String loophole(Integer x) { List ys = new LinkedList;
List xs = ys;
xs.add(x);
return (String) ys.iterator().next(); // run time error
}
When we extract an element from the list, and attempt to treat it as a string by casting it to String, we will get a ClassCastException. 当我们从列表中解出一个元素,并且试图通过将其转型为String,我们会得到一个ClassCastException。The exact same thing happens with the generic version of loophole().确切相同的事情会和loophole()的泛型版本。
The reason for this is, that generics are implemented by the Java compiler as a front-end conversion called erasure. 对于这种情况的原因是,泛型是通过Java编译器以从前端-后端转换称之为擦除来实现的。You can (almost) think of it as a source-to-source translation, whereby the generic version of loophole() is converted to the non-generic version.你可以认为它是源码到源码的转换,凭借这个loophole()的泛型版本被转换到非泛型版本。
As a result, the type safety and integrity of the Java virtual machine are never at risk, even in the presence of unchecked warnings.作为结果,类型安全和Java虚拟机的完整性不会处于危险状况,甚至在非检查警告出来的情况。
Basically, erasure gets rid of (or erases) all generic type information. 基本的,擦除所有泛型类型信息。All the type information betweeen angle brackets is thrown out, so, for example, a parameterized type like List<String> is converted into List.所有在三角括号之间的类型信息被抛出,因此,举个例子,参数和类型像List<String>会被转换为List。 All remaining uses of type variables are
replaced by the upper bound of the type variable (usually Object). 所有剩余类型变量的使用被类型变量的上界(通常是Object)替换。And, whenever the resulting code isn’t type-correct, a cast to the appropriate type is inserted, as in the last line of loophole.并且,任何时候当结果代码不是类型安全,就会有一个恰当的类型被插入,就像loophole的最后一行。
The full details of erasure are beyond the scope of this tutorial, but the simple description we just gave isn’t far from the truth. 擦出的详细信息已经超出这个教程的范围,但是这个我们已经给出的简单描述距离真实也不远。It’s good to know a bit about this, especially if you want to do more sophisticated things like converting existing APIs to use generics (see section 10), or just want to understand why things are the way they are.了解一些这些东西也比较好,特别如果你想更熟练的做使用泛型转换已存在的API的时候,或者仅仅想理解为什么事情是这样的。
6.3 Using Generic Code in Legacy Code
Now let’s consider the inverse case. 现在,让我们考虑相反的情况。Imagine that Fooblibar.com chose to convert their API to use generics, but that some of their clients haven’t yet. 想象一下Fooblibar.com选择使用泛型转换他们的API,但是他们的客户端却没有。So now the code looks like:因此,现在这个代码像这些:
package com.Fooblibar.widgets;
public interface Part { ...} public class Inventory { /**
* Adds a new Assembly to the inventory database.
* The assembly is given the name name, and consists of a set
* parts specified by parts. All elements of the collection parts
* must support the Part interface.
**/
public static void addAssembly(String name, Collection<Part> parts) {...} public static Assembly getAssembly(String name) {...}
}public interface Assembly { Collection<Part> getParts(); // Returns a collection of Parts
}
and the client code looks like:并且客户代码看起来像这样:
package com.mycompany.inventory;
import com.Fooblibar.widgets.*;
p...ublic class Blade implements Part {
}public class Guillotine implements Part {
}public class Main { public static void main(String[] args) { Collection c = new ArrayList();
c.add(new Guillotine()) ;
c.add(new Blade());
Inventory.addAssembly(”thingee”, c); // 1: unchecked warning
Collection k = Inventory.getAssembly(”thingee”).getParts();
}}
The client code was written before generics were introduced, but it uses the package com.Fooblibar.widgets and the collection library, both of which are using generic types. 客户代码在泛型被引入之前写的,但是它使用包com.Fooblibar.widgets和集合库,这两者都使用泛型类型。All the uses of generic type declarations in the client code are raw types.所有在泛型类型声明的使用时原始类型。
Line 1 generates an unchecked warning, because a raw Collection is being passed in where a Collection of Parts is expected, and the compiler cannot ensure that the raw Collection really is a Collection of Parts.第1行产生一个非检查警告,因为原始Collection被Part的Collection的地方传递,并且编译器不能确定原始Collection真正就是一个Part的Collection。
As an alternative, you can compile the client code using the source 1.4 flag, ensuring that no warnings are generated. 作为一种替代,你可以使用1.4的标志来编译客户端代码,确保没有警告产生。However, in that case you won’t be able to use any of the new language features introduced in JDK 1.5.然而,在这种情况下,你不能使用JDK1.5引入的任何新的语言特征。
7 The Fine Print
7.1 A Generic Class is Shared by all its Invocations所有调用共享的泛型类
What does the following code fragment print?下面的代码段打印什么?
List <String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
You might be tempted to say false, but you’d be wrong. 你可能试图说“假”,但是你错了。It prints true, because all instances of a generic class have the same run-time class, regardless of their actual type parameters.它打印为真,因为所有的泛型类的实例都具有相同的运行时类,而不管他们的实际类型参数。
Indeed, what makes a class generic is the fact that it has the same behavior for all of its possible type parameters; the same class can be viewed as having many different types.实际上,将一个类泛型实际上它具有了所有它的可能的类型参数的相同行为;相同类可以被看作具有许多不同的类型。
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.作为结果,类的静态变量和方法也同样在所有的实例中共享。这就是在静态方法或者初始化器中因为类型声明的类型参数为什么是非法的。
7.2 Casts and InstanceOf 转型和InstanceOf
Another implication of the fact that a generic class is shared among all its instances, is that it usually makes no sense to ask an instance if it is an instance of a particular invocation of a generic type:泛型类在其所有的实例中共享的另外一个暗示是它经常没有意识找出一个实例,如果它是泛型类型的特定调用的一个实例。
Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>) { ...} // illegal非法
similarly, a cast such as相似的,一个转型如下:
Collection<String> cstr = (Collection<String>) cs; // unchecked warning非检查告警
gives an unchecked warning, since this isn’t something the run time system is going
to check for you.给出一个非检查警告,因为在运行时系统不能为你做检查。
The same is true of type variables相同的是类型变量
<T> T badCast(T t, Object o) {return (T) o; // unchecked warning非检查警告
}
Type variables don’t exist at run time. 类型变量不会再运行时存在。This means that they entail no performance overhead in either time nor space, which is nice.这意味着他们既不产生超额的时间也不产生超额的空间损耗。Unfortunately, it also means that you can’t reliably use them in casts.不幸的是,这也意味着你不能可靠在转型中使用它们。
7.3 Arrays数组
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.你能够声明其元素类型是类型变量或者参数和类型的数组类型,但是不能声明数组对象。
This is annoying, to be sure. 这让人困扰,但事实却是这样。This restriction is necessary to avoid situations like:这个限制有必要避免如下的情况:
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运行时错误-ClassCastException
If arrays of parameterized type were allowed, the example above would compile without any unchecked warnings, and yet fail at run-time.如果参数化类型数组被允许,上面的例子应该在没有任何非检查警告的情况下编译,但是,实际上在运行时会出错。 We’ve had type-safety as a primary design goal of generics. 我们已经对泛型的主要设计目标具有类型安全的要求。In particular, the language is designed to guarantee that if your entire application has been compiled without unchecked warnings using javac -source 1.5, it is type safe.特别的,这个语言被设计来确保,如果你在使用javac –source 1.5没有任何非检查警告的情况下完成编译,它就是安全的。
However, you can still use wildcard arrays. 然而,你还是可以使用通配符数组。Here are two variations on the code above.这里有上述代码的两个版本。 The first forgoes the use of both array objects and array types whose element type is parameterized. 第一个放弃使用数组对象和其元素类型被参数化的数组类型。As a result, we have to cast explicitly to get a String out of the array.因此,我们不得不显式的从数组中转型以得到一个String。
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运行时错误,但是转型是显式的。
IIn the next variation, we refrain from creating an array object whose element type is parameterized, but still use an array type with a parameterized element type.在第二个版本中,我们避免创建元素类型被参数化数组对象,但是还是使用了参数化元素类型的数组类型。 This is legal, but generates an unchecked warning. 这个是合法的,但是产生一个非检查警告。Indeed, the code is unsafe, and eventually an error occurs.实际上,这些代码是非安全的,并且实际上也会产生错误。
List<String>[] lsa = new List<?>[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运行时错误,但是我们已经被警告过
Similarly, attempting to create an array object whose element type is a type variable causes a compile-time error:相似的,试图创建元素类型为类型变量的数组对象会导致编译时错误。
<T> T[] makeArray(T t) { return new T[100]; // error
}
Since type variables don’t exist at run time, there is no way to determine what the actual array type would be.因为,类型变量不会再运行时存在,没有办法决定实际数组类型是什么。
The way to work around these kinds of limitations is to use class literals as run time type tokens, as described in section 8.这个方法在这些种类的限制下工作是使用类字面量作为运行时类型符号,在第8节描述。
8 Class Literals as Run-time Type Tokens类字面量作为运行时类型符号
One of the changes in JDK 1.5 is that the class java.lang.Class is generic. JDK1.5中的一个变化就是java.lang.Class是泛型的。It’s an interesting example of using genericity for something other than a container class.对于除过容器类的某些东西使用泛型来说是一个令人感到有趣的例子。
Now that Class has a type parameter T, you might well ask, what does T stand for?现在,Class具有一个类型参数T,你可能会问,T表示什么? It stands for the type that the Class object is representing.它表示Class对象表示的类型。
For example, the type of String.class is Class<String>, and the type of Serializable. 举个例子,String.class的类型是Class<String>,并是Serializable的类型。class is Class<Serializable>. Class是Class<Serializable>.This can be used to improve the type safety of your reflection code.这可以用来提升你的反射代码中的类型安全。
In particular, since the newInstance() method in Class now returns a T, you can get more precise types when creating objects reflectively.特别的,因为newInstance()方法现在在Class中返回一个T,你可以在反射创建对象的时候获取更精细的信息。
For example, suppose you need to write a utility method that performs a database query, given as a string of SQL, and returns a collection of objects in the database that match that query.举个例子,假设你需要写一个工具方法来执行数据库查询,比如SQL串,并且返回满足这个查询的数据库中的对象的集合。
One way is to pass in a factory object explicitly, writing code like:一个方法是显式的传递一个工厂对象,像这样来写:
interface Factory<T> { T make();} public <T> Collection<T> select(Factory<T> factory, String statement) { Collection<T> result = new ArrayList<T>();
/* run sql query using jdbc */
for (/* iterate over jdbc results */ ) { T item = factory.make();
/* use reflection and set all of item’s fields from sql results */
result.add(item);
}return result;
}
You can call this either as你可以这样调用:
select(new Factory<EmpInfo>(){ public EmpInfo make() { return new EmpInfo();
}} , ”selection string”);
or you can declare a class EmpInfoFactory to support the Factory interface或者你可以声明一个类EmpInfoFactory来支持Factory接口
clas.s.. EmpInfoFactory implements Factory<EmpInfo> {
public EmpInfo make() { return new EmpInfo();}
}
and call it并且这样调用
select(getMyEmpInfoFactory(), ”selection string”);
The downside of this solution is that it requires either:这个解决方案的负面效果是需要如下条件之一:
• the use of verbose anonymous factory classes at the call site, or在调用点上冗余的匿名工厂类的使用,或者
• declaring a factory class for every type used and passing a factory instance at the call site, which is somewhat unnatural.为每一个被使用的类型声明一个工厂类,并且在调用点上传递一个工厂实例,这看起来不自然。
It is very natural to use the class literal as a factory object, which can then be used by reflection. 非常自然的使用类字面量作为工厂对象,这些能够被反射使用。Today (without generics) the code might be written: 今天(没有泛型)的代码可能这样写:
.C..ollection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”);
public static Collection select(Class c, String sqlStatement) { Collection result = new ArrayList();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) { Object item = c.newInstance();
/* use reflection and set all of item’s fields from sql results */
result.add(item);
}return result;
}
However, this would not give us a collection of the precise type we desire. 然而,这不能给我们需要的集合的准确的类型信息。Now that Class is generic, we can instead write现在,Class是泛型的,我们可以这样来写:
Collection<EmpInfo> emps =
... sqlUtility.select(EmpInfo.class, ”select * from emps”);
public static <T> Collection<T> select(Class<T>c, String sqlStatement) { Collection<T> result = new ArrayList<T>();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) { T item = c.newInstance();
/* use reflection and set all of item’s fields from sql results */
result.add(item);
} return result;
}
giving us the precise type of collection in a type safe way.在类型安全方法上,给了我们集合的精确类型。
This technique of using class literals as run time type tokens is a very useful trick to know. 使用类字面量的这种机制作为运行时类型标识是非常有用的技巧。It’s an idiom that’s used extensively in the new APIs for manipulating annotations, for example.在操纵注释的新的API中,这是一个被广泛使用的思想。
17
9 More Fun with Wildcards关于通配符更多有趣的东西
In this section, we’ll consider some of the more advanced uses of wildcards. 在这一部分,我们考虑使用通配符更高级的特性。We’ve seen several examples where bounded wildcards were useful when reading from a data structure. 我们已经看到从一个数据结构读取数据的时候使用边界通配符有用的地方。Now consider the inverse, a write-only data structure.现在我们考虑相反的情况,一个只写的数据结构。
The interface Sink is a simple example of this sort.接口Sink是这个例子
interface Sink<T> { flush(T t);
}
We can imagine using it as demonstrated by the code below. 我们可以想象通过下面的代码演示这个例子。The method writeAll() is designed to flush all elements of the collection coll to the sink snk, and return the last element flushed.方法writeAll()被设计来将集合coll中的所有元素清到snk中,并且返回最后一个被清掉的元素。
public static <T> T writeAll(Collection<T> coll, Sink<T> snk){ T last;
for (T t : coll) { last = t;
snk.flush(last);
}return last;
}...
Sink<Object> s;
Collection<String> cs;
String str = writeAll(cs, s); // illegal call非法调用
As written, the call to writeAll() is illegal, as no valid type argument can be inferred;当写的时候,调用writeAll()是非法的,因为没有被推断有效的类型参数;neither String nor Object are appropriate types for T, because the Collection element and the Sink element must be of the same type.String或者Object都不符合T,因为Collection元素和Sink元素必须是相同类型的。
We can fix this by modifying the signature of writeAll() as shown below, using a wildcard.我们可以通过使用一个通配符修改writeAll()的署名来修复这个问题。
public static <... T> T writeAll(Collection<? extends T>, Sink<T>){...}
String str = writeAll(cs, s); // call ok, but wrong return type调用没有问题,但是返回类型错误
The call is now legal, but the assignment is erroneous, since the return type inferred is Object because T matches the element type of s, which is Object.这个调用现在是合法的,但是赋值时错误的,因为返回类型推断应该是Object,因为T匹配s的元素类型,这个元素是Object。
The solution is to use a form of bounded wildcard we haven’t seen yet: wildcards with a lower bound. 解决方案是使用我们还没有用过的边界通配符:使用低边界的通配符。The syntax ? super T denotes an unknown type that is a supertype of T3. 语义?super T表明这个未知类型是T的超类型。It is the dual of the bounded wildcards we’ve been using, where we use ? extends T to denote an unknown type that is a subtype of T.边界通配符我们已经使用过,这里我们使用? extends T表明未知类型是T的子类。
.p..ublic static <T> T writeAll(Collection<T> coll, Sink<? super T> snk){...}
String str = writeAll(cs, s); // Yes!
Using this syntax, the call is legal, and the inferred type is String, as desired.使用这个符号,调用时合法的,并且推断类型是String,正如我们需要的那些。
Or T itself. Remember, the supertype relation is reflexive.否则就是T本身。记住超类型的关系是反身的。
Now let’s turn to a more realistic example. 现在,轮到更理想的例子。A java.util.TreeSet<E> represents a tree of elements of type E that are ordered. Java.util.TreeSet<E>表示顺序排列的类型为E的元素的树。One way to construct a TreeSet is to pass a Comparator object to the constructor. 构造一个TreeSet的方法是传递给构造器一个Comparator对象。That comparator will be used to sort the elements of the TreeSet according to a desired ordering.这个比较器将被用来对TreeSet的元素按照需要的顺序进行排序。
TreeSet(Comparator<E> c)
The Comparator interface is essentially:Comparator接口是很关键的:
interface Comparator<T> { int compare(T fst, T snd);
}
Suppose we want to create a TreeSet<String> and pass in a suitable comparator,
We need to pass it a Comparator that can compare Strings. 假设我们创建一个TreeSet<String>,并且传入一个合适的comparator,我们需要传入一个可以比较Strings的Comparator。This can be done by a Comparator<String>, but a Comparator<Object> will do just as well. However, we won’t be able to invoke the constructor given above on a Comparator<Object>.这件事情可以通过一个Comparator<String>来做,但是Comparator<Object>将做的一样好。然而,我们不能调用使用Comparator<Object>的构造器。We can use a lower bounded wildcard to get the flexibility we want: 我们能够使用下界通配符来获取我们想得到的灵活性:
TreeSet(Comparator<? super E> c)
This allows any applicable comparator to be used.这需要任何一个可用的比较器。
As a final example of using lower bounded wildcards, lets look at the method Collections.max(), which returns the maximal element in a collection passed to it as an argument.作为使用下界通配符的最后一个例子,让我看看方法Collections.max(),返回传递给它作为参数的集合的最大元素。
Now, in order for max() to work, all elements of the collection being passed in must implement Comparable. 现在,为了让max()工作,被传递的集合的所有的元素必须实现Comparable。Furthermore, they must all be comparable to each other.而且,他们必须彼此是相容的。
A first attempt at generifying this method signature yields第一次试图泛型化这个方法的署名
public static <T extends Comparable<T>>
T max(Collection<T> coll)
That is, the method takes a collection of some type T that is comparable to itself, and returns an element of that type. 这就是说,这个方法使用类型T的集合,其和它本身是兼容的,并且返回这种类型的元素。This turns out to be too restrictive. 这是很严格的限制。
To see why, consider a type that is comparable to arbitrary objects看看为什么, 考虑和任意对象都兼容的类型
.c.l.ass Foo implements Comparable<Object> {...}
Collection<Foo> cf = ...;
Collections.max(cf); // should work
Every element of cf is comparable to every other element in cf, since every such element is a Foo, which is comparable to any object, and in particular to another Foo. Cf的每一个元素和cf中的任何其他元素都是兼容的,因为每一个这种元素是一个Foo,都和任何对象兼容,特别是和另外一个Foo。However, using the signature above, we find that the call is rejected. 然而,使用上述的署名,我们发现调用被拒绝。The inferred type must be Foo, but Foo does not implement Comparable<Foo>.被推断的类型必须是Foo,但是Foo并没有实现Comparable<Foo>。
It isn’t necessary that T be comparable to exactly itself. 对于T必须和其本身明确兼容是不必要的。All that’s required is that T be comparable to one of its supertypes. 所需的是T必须和它的超类型兼容。This give us:
The actual signature of Collections.max() is more involved. Collections.max()的实际署名涉及到的更多。We return to it in section 10我们在第10节会再讨论
public static <T extends Comparable<? super T>>
T max(Collection<T> coll)
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>.这种原因应用到任何Comparable的使用上,其意图是对任意对象工作:你往往想到使用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). 通常,如果你具有仅仅将类型参数T作为参数的API,它的用法将得益于下界边界通配符(? super T)。Conversely, if the API only returns T, you’ll give your clients more flexibility by using upper bounded wildcards (? extends T).相反的,如果API仅仅返回T,你使用上界通配符(? extends T)来获取灵活性。
9.1 Wildcard Capture通配符捕获
It should be pretty clear by now that given现在可以给一个更清晰的例子
.S..et<?> unknownSet = new HashSet<String>();
/** Add an element t to a Set s */
public static <T> void addToSet(Set<T> s, T t) {...}
The call below is illegal.下面这个调用是非法的。
addToSet(unknownSet, “abc”); // illegal非法
It makes no difference that the actual set being passed is a set of strings; what matters is that the expression being passed as an argument is a set of an unknown type, which cannot be guaranteed to be a set of strings, or of any type in particular.它不能区分实际被传递了一组字符串的集合;这个表达式的被当作参数被传递的问题是一个位置类型集合,不能被确保是字符串的机会,或者某个特定的类型的集合。
Now, consider现在,考虑
.c.l.ass Collections {
<T> public static Set<T> unmodifiableSet(Set<T> set) { ... }
}...
Set<?> s = Collections.unmodifiableSet(unknownSet); // this works! Why?这是可以工作的!为什么?
It seems this should not be allowed; yet, looking at this specific call, it is perfectly safe to permit it. 看起来这是不能被允许的;然而,看这个特别的调用,它是安全被允许的。After all, unmodifiableSet() does work for any kind of Set, regardless of its element type.毕竟,unmaodifiableSet()对于任何种类的Set都是可以工作的,而不管它的元素类型。
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.这个规则被称之为通配符捕获,允许编译器推断未知类型的通配符作为一个泛型方法的类型参数。
10 Converting Legacy Code to Use Generics使用泛型转换遗留代码
Earlier, we showed how new and legacy code can interoperate. 前面,我们展示了如何把新代码和遗留代码放到一起互操作。Now, it’s time to look at the harder problem of “generifying” old code.现在,是时候来看看“泛型化”老代码比较难的问题。
If you decide to convert old code to use generics, you need to think carefully about how you modify the API.如果你决定使用泛型转换老代码,你需要仔细考虑你需要修改的API。
You need to make certain that the generic API is not unduly restrictive; it must continue to support the original contract of the API. 你需要确定泛型API是非常严格的;它必须继续支持这个API的原来的合约。Consider again some examples from java.util.Collection. 重新考虑Java.util.Collection中的某些例子。The pre-generic API looks like:泛型API看起来像这样:
interface Collection { public boolean containsAll(Collection c);
public boolean addAll(Collection c);
}
A naive attempt to generify it is:一个泛型化幼稚的企图是:
interface Collection<E> { public boolean containsAll(Collection<E> c);
public boolean addAll(Collection<E> c);
}
While this is certainly type safe, it doesn’t live up to the API’s original contract. 这是类型安全的,但是并不符合这个API原来的合约。The containsAll() method works with any kind of incoming collection.containsAll()方法和任何其他种类的导入参数一起工作。 It will only succeed if the incoming collection really contains only instances of E, but:如果导入集合真正的仅仅包含E的实例,它就是成功的,但是:
• The static type of the incoming collection might differ, perhaps because the caller doesn’t know the precise type of the collection being passed in, or perhaps because it is a Collection<S>,where S is a subtype of E.导入的集合的静态类型可能不同,可能因为调用者不知道被传入的集合的确切类型,可能因为它是Collection<S>,这里S是E的子类型。
• It’s perfectly legitimate to call containsAll() with a collection of a different type. The routine should work, returning false.使用不同类型的集合调用containsAll()是正确的。这个路径应该工作,返回false。
In the case of addAll(), we should be able to add any collection that consists of instances of a subtype of E. 在addAll()的例子中,我们应该能够增加由E的子类的实例组成的任何集合。We saw how to handle this situation correctly in section 5. 我们可以在第5节中看到如何正确的处理这种情况。
You also need to ensure that the revised API retains binary compatibility with old clients. 你也需要确定被修订的API和老的客户端保留二进制兼容。This implies that the erasure of the API must be the same as the original, ungenerified API. 这暗示API的擦除必须和原来的,没有泛型化的API一样。In most cases, this falls out naturally, but there are some subtle cases. 在大部分情况下,这种结果是自然的,但是还有某些微妙的情况。We’ll examine one of the subtlest cases we’ve encountered, the method Collections.max(). 我们将检查我们所遇到的最微妙的情况,方法Collections.max()。As we saw in section 9, a plausible signature for max() is:我们看第9节,看似合理的max()的署名:
public static <T extends Comparable<? super T>>
T max(Collection<T> coll)
This is fine, except that the erasure of this signature is这是好的,除过署名的擦除是
public static Comparable max(Collection coll)
which is different than the original signature of max():其和max()的原始数码是不同的:
public static Object max(Collection coll)
One could certainly have specified this signature for max(), but it was not done, and all the old binary class files that call Collections.max() depend on a signature that returns Object..可以确定max()的数码,但是这并不性,并且所有的老的class文件调用Collections.max()依赖于从返回Object的署名。
We can force the erasure to be different, by explicitly specifying a superclass in the bound for the formal type parameter T.我们可以迫使擦除湿不同的,通过显示的在形式参数T的边界中指定一个超类。
public static <T extends Object & 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. 这是一个给类型参数的多边界的例子,使用语义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.当使用多边界时,边界中的第一个提到的类型被用作类型变量的擦除。
Finally, we should recall that max only reads from its input collection, and so is applicable to collections of any subtype of T.最后,我们应该重新调用max仅仅从其输入的集合中读取,并且应用到T的所有的子类型的集合。
This brings us to the actual signature used in the JDK:这带给我们在JDK中使用的实际的署名:
public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll)
It’s very rare that anything so involved comes up in practice, but expert library designers should be prepared to think very carefully when converting existing APIs.对于涉及到的实践,这是非常稀少的,但是专家库设计者应该准备仔细的思考什么时候转换以存在的API。
Another issue to watch out for is covariant returns, that is, refining the return type of a method in a subclass. 另外一个问题是符合协变返回,这就是,精化子类中方法的返回类型。 You should not take advantage of this feature in an old API. 你不能在老的API中获得这种特征的好处。To see why, let’s look at an example. 为了探明为什么,让我看看一个例子。
Assume your original API was of the form假设原始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
}
Taking advantage of covariant returns, you modify it to:获得协变返回值的优点,你这些修改:
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
}
Now, assume a third party client of your code wrote现在,假设一个你写的代码的第三方客户:
public class Baz extends Bar { public Foo create(){...} // actually creates a Baz
}
The Java virtual machine does not directly support overriding of methods with different
return types. Java虚拟机不直接支持使用不同返回类型的方法的重写。This feature is supported by the compiler.这个特征仅仅被编译器支持。 Consequently, unless the class Baz is recompiled, it will not properly override the create() method of Bar.后果是,除非类Baz被重新编译,否则它就不能正确的重写Bar的create()方法。 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.而且,Baz将不得不被修改,因为代码会被拒绝-在Baz中的create()的返回值类型不是在Bar中的create()的返回值类型的子类型。
11 Acknowledgements声明
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, O¨ rjan 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.