Effective Java读书笔记、感悟——3.类和接口(一) 博客分类: Java_SE effectivejava类接口
最近被考试所压迫,半个月没看代码和看编程的书了,Mina系列代码研读也*暂停,还好昨天复习效率挺高,今天发现一只干一件事情也挺累的大脑,就空闲下来看了两节Effective Java,这一章比较多内容,就分成两次来写了。
一: 使类和成员变量的可访问性最小化
不多说,写一下作者提到的final数组的问题。
长度非0的数组总是可变的,所以类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。如果累具有这样的域或访问方法,客户端将能够修改数组中的内容,这事安全漏洞的一个常见根源:
public static final Thing[] VALUES = {…}
要注意,很多IDE会产生返回指向私有数组域的应用的访问方法,这样就产生了这个问题,修改这个问题一般有两种方法。
à使用公有数组变私有,并增加一个不可变的公有列表
public static final List<Thing> VALUES =
Collections.unmodifieableList(Arrays.asList(PRIVATE_VALUES));
à数组变味私有,增加一个公有方法,它返回私有数组的一个备份:
Public static final Thing[] values{
Return PRIVATE_VALUES.clone();
}
这里区别也很明显了,各适用于不同的问题。
二:在公有类中使用访问方法而非公有域
其实依然是这个原则,如果共有类暴漏了私有属性,那么将来改变其内部表示法是不可能的。
如果类是包级私有的,或者私有的嵌套类,直接暴漏它的数据域是没有本质错误的。
API中违反该规则的类如java.awt的Point、Dimension,但是是不提倡的。
三:使可变性最小化
要使类不可变(如String,基本类型的包装类,BigInteger,BigDecimal),要遵循五条规则:
<!--[if !supportLists]-->1. <!--[endif]-->不要提供任何会修改对象状态的方法
<!--[if !supportLists]-->2. <!--[endif]-->保证类不会被扩展
<!--[if !supportLists]-->3. <!--[endif]-->使所有的域都是final。
<!--[if !supportLists]-->4. <!--[endif]-->使所有的域都成为私有的。
<!--[if !supportLists]-->5. <!--[endif]-->确保对于任何可变组件的互斥访问。
不可变对象本质上是线程安全的,它们不要求同步,可以被*的共享,不可变对象为其他对象提供了大量的构件。不可变对象真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。
除非有很好的理由要让类成为可见类,否则都应该是不可变的。如果累不能被做成不可变的,仍然应该尽可能的限制它的可变性,降低对象可以存在的状态数。除非有令人信服的理由要使域变成非final的,否则要使每个域都是final的。
比如TimerTask,它是可变的,但是它的状态空间被有意地设计成非常小,可以创建一个TimerTask实例,并对它进行调度运行,也可以随意的取消,但是一旦定时器任务完成或者被取消就不能再次重新调度。
四:复合优先于继承
这里说的是实现继承,就是类的继承,接口的继承是没有问题的。
因为继承打破了封装性,而这正是可以复合的时候不推荐使用继承的最重要原因,而复合仅仅完成了转发。
这里不过多的解释作者的意思,因为含义也比较直观,正好前天上课刚用到的facade模式其实正是复合代替继承的很好方式,而且非常好的说明了Java不支持多继承带来的优越性(有时候你*要用复合代替继承)
实现Façade模式的时候通用的形式是IFacade接口实现所有的父接口,而IFaçade实现类提供所有子类统一界面是通过复合所有的父接口的实现类完成的,如果可以多继承可能就不会用到这样的机构,直接可以一个子类继承所有的父类,而这是不良好的编程方法。
随便搜了5、6篇Facade的博文,发现木有跟我说的非常相符合的图,还是详细画一下,虽然跟这个主题略有偏差,但图总是最清晰的。 首先找到一个文章中的一幅图,很形象的说明了Facade模式的作用,那么就从这个图入手来画一下具体实现Facade的类关系。
类的关系在上图中可以看到通过Facade很清晰了,那么下面这些部分只是单独的实现三个类做一次聚合,那么这个可靠性有点太单薄了,难道Facade可以任选实现什么方法?直接上图比较直接,就不多说了,看下图,进入下一小节了,希望在实现facade的时候有帮助。
五:要么为继承而设计,并提供文档说明,要么就禁止继承
类必须有文档说明它的可覆盖的方法的自用型。对于每个公有的或受保护的方法或者构造器,它的文档必须指明该方法或者构造器调用哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的。更一般的,类必须在文档说明,在哪些情况下它会调用可覆盖的方法。
对于为了继承而设计的类,唯一的测试方法就是编写子类。
构造器决不能调用可被覆盖的方法,无论直接调用还是间接调用。因为这种情况可能会发生错误,使得由于子类对调用方法的实现问题导致程序运行失败。
对于那些并非为了安全的进行子类化而设计和编写文档的类,要禁止子类化。