上星期去百田面试,被问到关于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也要考虑一些问题:
- 转换时要新建对象
- 类型信息必须不能和对象可变的状态关联
- Java泛型的各种不足(共享静态变量、没有泛型异常…)
这还让我想起了很久前想过的一个问题:区分子类的是什么?这个问题是我在看《重构与模式》时想到的,在Creation method那节,作者解释为什么在示例中几个类为什么不继承自一个抽象超类,因为在那个例子里:
- 区分类型的不是字段,而是计算数据的方式不同
- 不同类型之间转换比较方便
“计算数据的方式不同”在那里是用策略模式实现的,我想过把策略模式的实现改变一下,本来要为每一个策略建一个子类,现在直接在一个策略类里那里重载,由编译器根据Phantom type来选择策略。不过因为Java泛型是Type erasure实现的,运行时已经没了Phantom type的信息,重载不了。