假设您撰写了一个泛型类别:
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 及其上层的父类型态之对象。