Google

Archives

Java泛型中通配符使用

泛型是一种表示类或方法行为对于未知类型的类型约束的方法,比如 “不管这个方法的参数 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 for loop 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 written Collection<?> (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 from c and give them type 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. The add() method takes arguments of type E, the element type of the collection. 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. 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.On the other hand, given a List<?>, we can call get() 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 of get() to a variable of type Object or pass it as a parameter where the type Object is 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 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>. 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>. 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<? extendsShape>. Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List<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 of Shape. (Note: It could be Shape itself, or some subclass; it need not literally extend Shape.) We say that Shape is 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 into shapes in 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 of Shape
ode>. 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.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 as Person or its subtypes, such as Driver). 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--K for keys and V for 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类型,是传入的参数类型的交集

Related Posts

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>