本章主要讨论在设计类和接口的时候,要注意的事情以及一些设计原则。
这一条来源于面向对象设计的基本概念:封装。 这一概念认为,设计良好的模块应该隐藏所有的实现细节,只暴露其API给外部,内部的所有数据和实现都隐藏起来,模块间的互动由API完成。 这样做的好处是将模块之间的耦合度尽可能地降低了,在以后的开发中,设计者只需要在改动API的时候考虑到带给用户的影响以及兼容性,而其他隐藏部分的改动,只要不破坏API的约定,那么修改过程就是完全对用户透明的。 这么做不会带来性能上的优势,但是使得代码更加容易测试和维护,开发人员无需顾此失彼,只需要专注注意力在一处即可。 JAVA这门语言作为面向对象语言的标杆,自然提供了完善的机制来支持封装这一设计理念。 所有的顶层类和接口都有两个访问级别: - 包级私有:意味着只有同个包下的类能够访问 - 公有:所有类都可访问
所有的顶层类内部的成员(域、方法、成员类、内部接口)都有四种可能的访问级别: - 私有的:只有该顶层类内部可以访问 - 包级私有:只有同一个包下面的类能够访问(默认的访问级别,不写关键字则为该级别) - 受保护的:同一个包下面的类能够访问,顶层类的子类也能够访问 - 公有的:所有的类都可以访问
有了以上这些访问级别的设定,我们所写代码的可见性就可以受到控制。让那些该被看见的类或者方法被看见,不该被看见的类或者方法不要被看见。 在上面的访问级别中,当你把顶层类或者接口设置为公有的,那么就代表它开放给用户客户端使用,就代表你有责任永远支持它并保持后续兼容性。 如果你设置为包级私有的,也就是在类名前面不加任何相应的关键字,那么就代表它不会开放给用户客户端使用,它是开放给模块内部使用的,你可以随时地修改它,甚至删除它,因为你确信用户不会直接地使用到它,用户甚至都感知不到它的存在。
类内部的成员则要复杂一些,首先,所有内部的域,除了不可变常量以外,都应该设置为私有的 如果你设置为其他级别,就等于放弃了控制该域的能力,外界可以直接引用该域,你不能在外界引用该域的时候做任何事,也不能在外界改变该域的时候感知并做任何事,你甚至放弃了未来会删除该域的可能性,改名的可能性,线程安全的可能性。 另外,注意受保护的
这一访问级别,看起来它像是包级私有的孪生兄弟,然而实际上它和公有的一样拥有很强的破坏性。在一个公有的类中设置一个受保护的域,那么任何继承该类的子类都能访问该受保护的域,即使不在同一个包也一样,不怀好意的黑客完全可以使用这个漏洞篡改你以为外界没有机会篡改的数据。 你还要注意数组域,数组也绝不能声明为公有的,即使被声明为final也无济于事,因为外界还是可以修改数组内部的对象。
这和上一条没有什么本质上的不同,理由也很简单就是采取封装的设计理念,尽可能地减小与外界的耦合。 如果非要让外界访问数据不可,那就提供一个公有的访问方法,作为合法访问数据的唯一途径,来保证即使在未来你想要改变数据的结构,客户端代码仍能有效地获得数据。
本条主要在讲一个叫做不可变类的东西,这种类一旦实例化永远不能再被修改,因此它的状态一出生就被确定下来,任何人都不能改变。 ### 不可变类有什么优点? 优点如下: - 不可变对象比较简单。它出生后只有一种状态,而可变类可有非常多的状态,随着其内部域的增加,其状态空间指数级增长。由于不可变对象非常简单,几乎不需要程序员额外维护其状态。 - 不可变对象本质上是线程安全的,不需要同步。由于不可变对象永远只有一种状态,因此它可以被自由地共享于不同的线程。也无需保护性拷贝 - 不可变对象的内部信息也可以共享,比如BigInteger内部使用符号和数值分开表示的方法,数值使用int数组表示,当使用negate方法时,会得到一个新的BigInteger,它的符号相反,但是数值仍然是相同的int数组,无需复制一个新的数组给新对象。为什么可以这样做?因为确信原来的对象其内部的数组也是不可变的。
不可变对象在某些时候有性能问题。 第一种情况是:面对巨大的对象的时候。一个对象内部有一个上万个元素的数组,如果使用不可变对象,即使只是想要改变数组内部的一个元素而已,也要再创建一个新的,内部有上万个元素的数组的对象。这势必造成内存浪费和性能低下。 第二种情况是:一系列连续的步骤,每个步骤都会产生一个不可变对象,导致最终步骤结束时产生了无数个中间对象,而它们都被立刻抛弃浪费。这造成了多余的创建对象和回收对象的开销。
遵循以下五条规则: 1. 不提供任何会改变对象状态的方法 2. 保证类不会被继承,以防止子类假装对象的状态已经改变 3. 使对象所有的域都是final的 4. 使所有的域都为私有的 5. 对于任何可变的对象引用做到互斥访问,即防止其内部的可变对象的引用被外部获取到,从而可以从外部直接改变其内部的可变对象。为此,必须做到不直接使用外部传递的对象引用来初始化对象,不给外部返回任何内部的可变对象引用,如果非要返回给外部,使用保护性拷贝返回给外部拷贝的对象。
在想办法满足上面第2条的时候,通常简单的选择是使类变为final的,这样子类就没法继承它。 还有一种方法,就是使类的所有构造器都为私有的或者包级私有的,用公有静态工厂方法来返回实例。这样子类便没法覆盖父类,还能利用静态工厂的诸多好处。