`
javatar
  • 浏览: 1682421 次
  • 性别: Icon_minigender_1
  • 来自: 杭州699号
社区版块
存档分类
最新评论

关于Java泛型违反Liskov原则

阅读更多
Java5 增加的泛型语法,使类型模板的应用得到了提升,但它的运行期擦拭的做法(为向前兼容),令人诟病,
使得一个Map集合,通过反射拿到的集合元素的泛型类型,不是实际使用类型,而是K和V(字节码编译期保留)。
另一个有争议的地方是:
泛型违反了里氏代换原则(Liskov's Substitution Principle),即:子类应该在任何地方都能替换父类。
假设一个函数:
void xxx(List<Object> list);

调用:
List<String> list = ...
xxx(list); // 编译出错

当然,你可以使用下面的变通方式就不会出错:
void xxx(List<? extends Object> list);


主要的问题在于:List<String> 是不是 List<Object> 的子类?
我觉得应该是,至少“人”认为是,面象对象的主要目的是什么?就是让人理解程序,而不是机器。
而官方给出的答案却是:List<String> 不是 List<Object> 的子类
Java泛型规格说明书 写道

让我们测试一下我们对泛型的理解。下面的代码片断合法么?

List<String> ls = new ArrayList<String>(); //1

List<Object> lo = ls; //2

第1行当然合法,但是这个问题的狡猾之处在于第2行。

这产生一个问题:

一个String的List是一个Object的List么?大多数人的直觉是回答:“当然!”。

好,在看下面的几行:

lo.add(new Object()); // 3

String s = ls.get(0); // 4: 试图把Object赋值给String

这里,我们使用lo指向ls。我们通过lo来访问ls,一个String的list。我们可以插入任意对象进去。结果是ls中保存的不再是String。当我们试图从中取出元素的时候,会得到意外的结果。

java编译器当然会阻止这种情况的发生。第2行会导致一个编译错误。

总之,如果Foo是Bar的一个子类型(子类或者子接口),而G是某种泛型声明,那么G<Foo>是G<Bar>的子类型并不成立!!

这可能是你学习泛型中最难理解的部分,因为它和你的直觉相反。

这种直觉的问题在于它假定这个集合不改变。我们的直觉认为这些东西都不可改变。

举例来说,如果一个交通部(DMV)提供一个驾驶员里表给人口普查局,这似乎很合理。我们想,一个List<Driver>是一个List<Person>,假定Driver是Person的子类型。实际上,我们传递的是一个驾驶员注册的拷贝。然而,人口普查局可能往驾驶员list中加入其他人,这破坏了交通部的记录。

为了处理这种情况,考虑一些更灵活的泛型类型很有用。到现在为止我们看到的规则限制比较大。


上面所描述的语义限制根本没有意义,如果想限制用户在List<String>中加入Object, 因为泛型的运行期擦拭,等于白做。
因为想做这个语义上的限制,而牺牲直观的理解非常不值,加一个"?"问号作为替代方案,只会使泛型更复杂,使用也不方便。
用户必须在"?"问号与"Object"间绕来绕去,烦也不烦,或许可以问:"?"问号等于"Object"吗?呵呵,maybe.
2
1
分享到:
评论
3 楼 mercyblitz 2010-03-15  
其实我觉得擦写是没有意义,导致了运行时参数类型无法获取。

2 楼 javatar 2008-11-11  
或许文章的标题有些问题,Liskov原则是继承体系的基本,Sun不会去违反它的,只是Sun将List<String>解释成不是List<Object>的子类,而是List<?>的子类,有些让人费解,无意义的增加了泛型的复杂性。
1 楼 非常菜 2008-11-10  
找到一个定义 Liskov Substitution Principle LSP:
一个软件实体如果使用的是一个基类的话那么一定适用于其子类,而且它察觉不出基类对象和子类对象的区别。也就是说,在软件里面,把基类都替换成它的子类,程序的行为没有变化。
出处:http://aladdin.iteye.com/blog/40810
说的是,对于基类的使用可以用子类来代替,程序行为未改变。

就拿Object和String的例子,我觉得可以这么套用定义:
所有可以使用Object的地方,可以用String代替。
但,所有可以用List<Object>的地方,能否用List<String>代替?泛型的回答是:否。

楼主的问题在于,泛型违反LSP的做法是否值得?

从我个人的角度考虑,有了泛型之后,对于型别检查变得更方便了,虽然违反LSP,但是对于开发的效率和减少开发中关于型别不一致导致的低级错误的减少是大有裨益的。另外,在泛型中引入继承机制,也是为了增加java的灵活性。
我想Sun的工程师们,也是经过较长时间考虑才做出这样设计的吧。

相关推荐

Global site tag (gtag.js) - Google Analytics