ITEEDU

Java Gossip: 定义泛型类别

当您定义类别时,发现到好几个类别的逻辑其实都相同,就只是当中所涉及的型态不一样时,使用复制、贴上、取代的功能来撰写程序只是让您增加不必要的档案管理困扰。

由于Java中所有定义的类别,都以Object为最上层的父类别,所以在 J2SE 5.0 之前,Java程序设计人员可以使用Object来解决上面这样的需求,为了让定义出来的类别可以更加通用(Generic),传入的值或传回的对象都是以Object为主,当您要取出这些对象来使用时,必须记得将接口转换为原来的类型,这样才可以操作对象上的方法。

然而使用Object来撰写泛型类别(Generic Class)留下了一个问题,因为您必须要转换接口,粗心的程序设计人员往往会忘了要作这个动作,或者是转换接口时用错了型态(像是该用Boolean却 用了Integer),要命的是,语法上是可以的,所以编译器检查不出错误,真正的错误要在执行时期才会发生,这时恼人的ClassCastException就会出来搞怪,在使用Object设计泛型程序时,程序人员要再细心一些、小心一些。

在J2SE 5.0之后,提出了针对泛型(Generics)设计的解决方案,要定义一个简单的泛型类别是简单的,直接来看个例子:

GenericFoo.java
public class GenericFoo<T> {
	private T foo;
	public void setFoo(T foo) {
		this.foo = foo;
	}
	public T getFoo() {
		return foo;
	}
}

<T> 用来宣告一个型态持有者(Holder)T,之后您可以用 T 作为型态代表来宣告变量(参考)名称,然后您可以像下面的程序来使用这个类别:

GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
GenericFoo<Integer> foo2 = new GenericFoo<Integer>();

foo1.setFoo(new Boolean(true));
Boolean b = foo1.getFoo();

foo2.setFoo(new Integer(10));
Integer i = foo2.getFoo();

不同的地方在于,在宣告与配置对象时,您可以一并指定泛型类别中真正的型态,这将用来取代定义时所使用的 T,而这次您可以看到,接口转换不再需要了,所定义出来的泛型类别在使用时多了一层安全性,至少可以省去恼人的ClassCastException 发生,编译器可以帮您作第一层防线,例如下面的程序会被检查出错误:

GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();

foo1.setFoo(new Boolean(true));
Integer b = foo1.getFoo();

foo1.getFoo()传回的是Boolean,您要将它指定给Integer,这显然语法上不合,编译器这时可以派上用场:

Test.java:7: incompatible types
found : java.lang.Boolean
required: java.lang.Integer
Integer b = foo1.getFoo();

如果使用泛型类别,但宣告时不一并指定型态呢?那么预设会使用Object,不过您就要自己转换对象的接口型态了,但编译器会提出警讯,告诉您这可能是不安全的操作:

Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

回过头来看看下面的宣告:

GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
GenericFoo<Integer> foo2 = new GenericFoo<Integer>();

GenericFoo< Boolean>宣告的foo1与GenericFoo< Integer>宣告的foo2是相同的类型吗?答案是否定的,基本上它们分属于两个不同类别的类型,即「相当于」下面两个类型(只是个比喻):

public class GenericFooBoolean {
	private Boolean foo;
	public void setFoo(Boolean foo) {
		this.foo = foo;
	}
	public Boolean getFoo() {
		return foo;
	}
}

以及:

public class GenericFooInteger {
	private Integer foo;
	public void setFoo(Integer foo) {
		this.foo = foo;
	}
	public Integer getFoo() {
		return foo;
	}
}

所以您不可以将 foo1 指定给 foo2,或是将 foo2 指定给 foo1,编译器会回报以下错误:

incompatible types
found : GenericFoo<java.lang.Integer>
required: GenericFoo<java.lang.Boolean>
foo1 = foo2;