泛型是一种表示类或方法行为对于未知类型的类型约束的方法,比如 “不管这个方法的参数 x 和 y 是哪种类型,它们必须是相同的类型”,“必须为这些方法提供同一类型的参数” 或者 “foo() 的返回值和bar() 的参数是同一类型的”。虽然 String 是 Object 的子类,但是 List<String> 和 List<Object> 之间并没有什么关系——List<String> 不是 List<Object> 的子类或者子类型。
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 (i.e., a pre-5.0 release):
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
forloop syntax):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!So what is the supertype of all kinds of collections? It’s writtenCollection<?>(pronounced “collection of unknown”), that is, a collection whose element type matches anything. It’s called a wildcard type for obvious reasons. 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 fromcand give them typeObject. 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 errorSince we don’t know what the element type of
cstands for, we cannot add objects to it. Theadd()method takes arguments of typeE, the element type of the collection. When the actual type parameter is?, it stands for some unknown type. Any parameter we pass toaddwould have to be a subtype of this unknown type. Since we don’t know what type that is, we cannot pass anything in. The sole exception isnull, which is a member of every type.On the other hand, given aList<?>, we can callget()and make use of the result. The result type is an unknown type, but we always know that it is an object. It is therefore safe to assign the result ofget()to a variable of typeObjector pass it as a parameter where the typeObjectis expected.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
Canvasthat 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 exactlyShape: it cannot, for instance, be called on aList<Circle>. That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on aList<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>withList<? extendsShape>. NowdrawAll()will accept lists of any subclass ofShape, so we can now call it on aList<Circle>if we want.List<? extends Shape>is an example of a bounded wildcard. 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 ofShape. (Note: It could beShapeitself, or some subclass; it need not literally extendShape.) We say thatShapeis the upper boundof the wildcard.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 intoshapesin the body of the method. 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 ofShape
ode>. Since we don't know what type it is, we don't know if it is a supertype ofRectangle; it might or might not be such a supertype, so it isn't safe to pass aRectanglethere.Bounded wildcards are just what one needs to handle the example of the DMV passing its data to the census bureau. Our example assumes that the data is represented by mapping from names (represented as strings) to people (represented by reference types such asPersonor its subtypes, such asDriver).Map<K,V>is an example of a generic type that takes two type arguments, representing the keys and values of the map.Again, note the naming convention for formal type parameters--Kfor keys andVfor values.
public class Census { public static void addRegistry(Map<String, ? extends Person> registry) { } ... Map<String, Driver> allDrivers = ... ; Census.addRegistry(allDrivers);
转自:http://www.blogjava.net/killme2008/archive/2007/06/05/122174.html 泛型引入java语言已经有很长一段时间了,在JDK5出来的时候也非常认真地学习过,不过学习的资料都是网上泛滥并且重复的教程。这几天下了《The Java Programming Language》的第4版,准备把jdk5引入的新东西再重新系统地学习一次,同时再次回顾下java基础。今天记录下学习泛型那一章的注意点。一、泛型类型的声明1.需要着重注意的一点,比如声明类Cell<E>:
package net.rubyeye.javaprogramming.generic;
public class Cell<E> { private Cell<E> next; private E element; public Cell(E element) { this.element = element; } public Cell(E element, Cell<E> next) { this.next = next; this.element = element; } public E getElement() { return element; } public void setElement(E element) { this.element = element; } public Cell<E> getNext() { return next; } public void setNext(Cell<E> next) { this.next = next; }}然后如此使用:
Cell<String> strCell = new Cell<String>(“Hello“);Cell<Integer> intCell = new Cell<Integer>(25);那么Cell<String>和Cell<Integer>是两个类吗?不,他们是同一个类,通过下面的实验证明:
assertTrue(strCell.getClass() == intCell.getClass()));java泛型的实现采用的“擦拭法”,Cell<E>仍然是一个类,无论E被任何具体的类型所替代。 2.泛型的类型参数不能用于static变量、static方法和static初始化,比如下面的使用方式都不能编译通过:
public class Cell<E> { private static Cell<E> next; private static void test(E e){ } 同样,静态方法是与类相关联的,调用也只能通过类,假设Cell有一个静态方法test,怎么调用才是正确的呢?
Cell<E>.test(); //编译错误Cell<String>.test(); //同样编译错误Cell.test(); //正确的方式类似的,泛型的类型参数不能用于声明数组类型,比如下面的代码同样无法编译通过:
class SingleLinkQueue<E> { //
public E[] toArray() { //
}}
3.类型参数可以继承其他的类和接口,如果有多个接口可以用&符号连接,通过extend参数限制了类型参数的范围,比如:
interface SortedCharSeqCollection<E extends Comparable<E> & CharSequence> { //
sorted char sequence collection methods
}
SortedCharSeqCollection的类型参数E强制继承自Comparable和CharSequence接口,也就是替代的具体的类型参数必须实现这两个接口,从而限制了类型参数(type parameter)。4.比较有趣的内部类的泛型,对于静态内部类的类型参数可以与外部类的类型参数名不一样,静态内部类的类型参数与外部类的类型参数其实没有一点关系,比如:
class SingleLinkQueue<E> { static class Cell<E> { private Cell<E> next; private E element; public Cell(E element) { this.element = element; } public Cell(E element, Cell<E> next) { this.element = element; this.next = next; } public E getElement() { return element; } /*
rest of Cell methods as before
*/ } protected Cell<E> head; protected Cell<E> tail; /*
rest of SingleLinkQueue methods as before
*/}Cell<E>类的声明和SingleLinkQueue<E> 两个类中的E仅仅是名称相同,他们之间的关联是通过head和tail的声明才关联在一起,你可以将Cell<E>中的E改成F也没关系,比如:
package net.rubyeye.javaprogramming.generic;
class AnotherSingleLinkQueue<E> {
static class Cell<F> {
private Cell<F> next; private F element;
public Cell(F element) {
this.element = element; }
public Cell(F element, Cell<F> next) {
this.element = element; this.next = next;
}
public F getElement() { return element; } /*
rest of Cell methods as before 
*/ } protected Cell<E> head; protected Cell<E> tail; /*
rest of SingleLinkQueue methods as before
*/}而一般的内部类就不一样了,内部类可以直接使用外部类的类型参数甚至隐藏。
二、子类型与通配符今天读了第2节,泛型的使用比我原先所知的更为复杂,java语法本来以简洁优美著称,随着java5,java7的到来,语法是越来越复杂,甚至可以说丑陋!-_- 要知道一点,比如List<Integer>不是List<Number>的子类,而是Collection<Integer>的子类。因为List<Integer>和List<Number>的类型是一样的,都是List。那么如何表示参数化类型是Number的子类呢?这就需要用到通配符:
List<? extends Number>
表示类型变量是Number或者Number的子类。这个就是所谓的上界通配符,同样,如果要表示类型变量是Number或者Number的super type,可以使用下界通配符:
List<? super Number>
而通配符List<?>等价于:
List<? extends Object>
通配符只能用于变量、局部变量、参数类型和返回类型,不能用于命名类和接口。比如下面的代码将不能编译通过:
class MyList implements List<?>{ //
} 通配符有另一个问题:因为通配符代表的是未知的类型,你不能在任何需要类型信息的地方使用它。比如下面的代码同样无法编译通过:
>
SingleLinkQueue<?> strings = new SingleLinkQueue<String>();strings.add(“Hello“); // INVALID: 无法编译SingleLinkQueue<? extends Number> numbers = new SingleLinkQueue<Number>();numbers.add(Integer.valueOf(25)); // INVALID: 无法编译三、泛型方法和类型推断 如果我们想参数化方法的参数和返回值的类型,这就引出了泛型方法的声明,声明一个泛型方法的方式如下:
<T> T passThrough(T obj) { return obj;}这个方法限制传入的参数的类型与返回的参数类型将一致,可以看到,在方法签名前加上<T>即可。我们可以这样调用这个方法:
String s1 = “Hello“;String s2 = this.<String>passThrough(s1);这样的调用是不是比较奇怪?幸好提供了类型推断,根据参数的类型来自动判断方法的类型(比如返回值类型),因此可以直接调用:
String s1 = “Hello“;String s2 = this.passThrough(s1); 如果方法有两个类型变量,类型推断将怎么处理呢?比如:
<T> T passThrough(T obj1,T obj2) { return (T)(obj1.toString()+obj2.toString()); }然后我们传入两个参数,一个String,一个int,那么返回什么呢?
String s1=“test“;String s3=this.passThrough(s1, 1); //编译出错类型推断是比较复杂的,这里将返回的将是Object类型,是传入的参数类型的交集
