细数jdk库的四宗罪

写过java代码的编程人员都有这种感慨——jdk库非常庞大,并且有非常多精心设计的工具可以拿来用。另一方面,jdk版本的向下兼容性也做得非常好,升级版本对旧项目来说没多大困难。由于这两点原因,jdk不可避免地存在一些设计上的缺陷。为了保障旧项目开发团队的利益,这些设计缺陷只能随着众多版本被继承下来。

个人总结了一下,下面几点我们可以把它当作反面教材。


第一宗罪:破坏封装性,公有类直接暴露数据域

如果问一个程序员,为什么javabean不直接将属性设为public,而需要浪费那么多精力搞一堆getter/setter方法。很多人会回答——为了让代码看起来更加oop。

其实这个回答没有抓住重点。我们之所以要实现getter/setter方法,依我看来,至少有两点原因:1、允许我们在调用方法时作限制与记录,例如对setter的参数作边界的判断,在调用方法时增加日志或断点调试;2、可以保持对外开放接口不变的同时修改内部数据结构。

JDK库的几个类破坏了这种封闭性,比较典型的是awt包里的Dimension和Point两个类。从源代码可以看出,这两个类的属性域都直接是public修饰的。


第二宗罪:滥用继承

util包有一个工具类,明显是滥用继承的代表。没错,这个类就是鼎鼎大名的Stack类了。

我们知道Statck数据结构本身的特点就是“后进先出”。那么它理想就应该只有两个方法来操纵数据,一个是push()方法,另一个是pop()方法。但是由于继承了Vector,Stack从父亲那里继承了诸如add(),remove(),set()等违反游戏规则的方法。这简直无法直视!!


第三宗罪:值对象应该设计为不可变对象

不可变对象是指,实例属性经过初始化后在对象的整个生命周期内固定不变。例如jdk的String以及各种基本类型的包装类。

不可变对象易于设计,并且在并发环境下更加安全,无需额外的同步机制。

util包里的Date类属于值对象,类似于人民币一元,十元的概念,应该被设计为不可变对象。也就是说,不应该提供各种setter方法来修改对象的属性域。


第四宗罪:使用常量接口

常量接口是指没有任何方法,只包含表态final域的接口对象,每一个域都导出一个。实现该接口的类即可获得接口的所有常量属性,看起来非常方便!

然而,常量接口模式是对接口的不良使用。首先,常量的使用应属于内部实现细节,实现常量接口会把这样的细节暴露到导出的API中。其次,如果在后续升级中发现一个类不再需要以前的常量,这个类依然必须实现这个常量接口,以确保向下兼容性。

jdk平台io包里的ObjectStreamConstants就属于常量接口,不值得效仿。

那么,如果需要导出常量,最好的选择方案是jdk5引入的枚举类型(enum)。当然,如果某些常量跟某个类或接口联系非常紧密,也可以把这个常量绑定在该类,例如Integer.MAX__VALUE或Math.PI等。

然而,某些开发人员还是喜欢选择常量接口,因为枚举略显繁琐,而类又必须写一串长长的"public static final "修饰。但是他们的使用还是有底线的,当需要某个常量的,代码只会用XXConstants.XX的模式,而不会直接让类实现这个接口。这种使用算是一种折中方案。采用哪种方法,还是看项目规范吧。


考虑到jdk库的庞大以及兼容性,以上列出的几个“瑕疵”也就瑕不掩瑜,无可厚非了。jdk的源码还是非常值得我们好好学习与揣摩的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jforgame

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值