大可制作:QQ群:31564239(asp|jsp|php|mysql)

Java Gossip: 类型通配字节

假设您撰写了一个泛型类:
  • GenericFoo.java
public class GenericFoo<T> {
private T foo;

public void setFoo(T foo) {
this.foo = foo;
}

public T getFoo() {
return foo;
}
}

分别使用下面的程序声明了foo1与foo2两个引用名称:
GenericFoo<Integer> foo1 = null;
GenericFoo<Boolean> foo2 = null;
 

那么 foo1 就只接受GenericFoo<Integer>的实例,而foo2只接受GenericFoo<Boolean>的实例。

现在您有这么一个需求,您希望有一个引用名称foo可以接受所有下面的实例(List、Map或List接口以及其实接口的相关类,在J2SE 5.0中已经针对泛型功能作了改写,在这边仍请将之当作接口就好,这是为了简化说明的考量):
foo = new GenericFoo<ArrayList>();
foo = new GenericFoo<LinkedList>();
 

简单的说,实例化类型持有者时,它必须是实现List的类或其子类,要声明这么一个引用名称,您可以使用 '?' 通配字节,并使用"extends"关键字限定类型持有者的类型,例如:
GenericFoo<? extends List> foo = null;
foo = new GenericFoo<ArrayList>();
.....
foo = new GenericFoo<LinkedList>();
....
 

如果指定了不是实现List的类或其子类,则编译器会回报错误,例如:
GenericFoo<? extends List> foo = new GenericFoo<HashMap>();
 

上面这段程序编译器会回报以下的错误:
incompatible types
found : GenericFoo<java.util.HashMap>
required: GenericFoo<? extends java.util.List>
GenericFoo<? extends List> foo = new GenericFoo<HashMap>();

这样的限定是很有用的,例如如果您想要自订一个showFoo()方法,方法的内容实现是针对List而制定的,例如:
public void showFoo(GenericFoo foo) {
     // 针对List而制定的内容
}
 

您当然不希望任何的类型都可以传入showFoo()方法中,您可以使用以下的方式来限定,例如:
public void showFoo(GenericFoo<? extends List> foo) {
     //
}
 

这么一来,如果有粗心的程序设计人员传入了您不想要的类型,例如GenericFoo<Boolean>类型的实例,则编译器都会告诉它这是不可行的,在声明名称时如果指定了<?>而 不使用"extends",则默认是允许Object及其下的子类,也就是所有的Java对象了,那为什么不直接使用GenericFoo声明就好了,何 必要用GenericFoo<?>来声明?使用通配字节有点要注意的是,透过使用通配字节声明的名称所引用的对象,您没办法再对它加入新的信息,您只能取得它的信息或是移除它的信息,例如:
GenericFoo<String> foo = new GenericFoo<String>();
foo.setFoo("caterpillar");
GenericFoo<?> immutableFoo = foo;

// 可以取得信息
System.out.println(immutableFoo.getFoo());

// 可透过immutableFoo来移去foo所引用实例内的信息
immutableFoo.setFoo(null);

// 不可透过immutableFoo来设定新的信息给foo所引用的实例
// 所以下面这行无法通过编译
//  immutableFoo.setFoo("良葛格");

所以使用<?>或是<? extends SomeClass>的声明方式,意味着您只能透过该名称来取得所引用实例的信息,或者是移除某些信息,但不能增加它的信息,因为只知道当中放置的 是SomeClass的子类,但不确定是什么类的实例,编译器不让您加入对象,理由是,如果可以加入对象的话,那么您就得记得取回的对象实例是什么形态, 然后转换为原来的类型方可进行操作,这就失去了使用泛型的意义。

事实上,GenericFoo<?> immutableFoo相当于GenericFoo immutableFoo


除了可以向下限制,您也可以向上限制,只要使用"super"关键字,例如:
GenericFoo<? super StringBuilder> foo;
 
如此,foo就只接受 StringBuilder 及其上层的父类类型之对象。