ITEEDU

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 及其上层的父类型态之对象。