Java的泛型和Phantom type

October 24, 2013

上星期去百田面试,被问到关于Java泛型的理解,那时候我的回答就是“参数化类型”1,准确的讲来说还是bounded的…下面的泛型都指Java的泛型。

我见过最有趣的泛型的用法就是实现Phantom type。

interface IsSafe {}
interface Safe extends IsSafe {}
interface UnSafe extends IsSafe {}

public class Input<T extends IsSafe> {
	String str;
	Input(String str) {
		this.str = str;
	}

	static Input<Safe> escape(Input<UnSafe> unsafe) {
		return new Input<Safe>(unsafe.str);
	}
}

IsSafe、Safe、UnSafe这3个接口只作为类型信息,用来标记一个Input是否安全,escape()的作用就是把一个不安全的Input转成安全的。这样就可以做到在编译期保证需要Input<Safe>的方法不会得到Input<UnSafe>或者其他东西2,除非你强转了。

使用Phantom type也要考虑一些问题:

  1. 转换时要新建对象
  2. 类型信息必须不能和对象可变的状态关联
  3. Java泛型的各种不足(共享静态变量、没有泛型异常…)

这还让我想起了很久前想过的一个问题:区分子类的是什么?这个问题是我在看《重构与模式》时想到的,在Creation method那节,作者解释为什么在示例中几个类为什么不继承自一个抽象超类,因为在那个例子里:

  1. 区分类型的不是字段,而是计算数据的方式不同
  2. 不同类型之间转换比较方便

“计算数据的方式不同”在那里是用策略模式实现的,我想过把策略模式的实现改变一下,本来要为每一个策略建一个子类,现在直接在一个策略类里那里重载,由编译器根据Phantom type来选择策略。不过因为Java泛型是Type erasure实现的,运行时已经没了Phantom type的信息,重载不了。

更多Phantom type在Java和C#里的用法:1, 2, 3


  1. 在面试的时候,我有一个小声“弱爆的”脱口而出,不知道是不是在说Java的泛型。

  2. 当然你建两个Input的子类: SafeInput,UnSafeInput,也是能达到类似的类型安全要求。