假设您撰写了一个泛型类:
public class GenericFoo<T> { 分别使用下面的程序声明了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 及其上层的父类类型之对象。 |