4.类和接口
15. 最小化类和成员的访问权限
尽量让每个类或成员无法访问
四种访问级别
private -- 只有声明该成员的类本身能访问
package-private -- 包内的类都可以访问,默认访问级别
protected -- 子类和包内类可以访问
public -- 谁都可以访问
公共类的成员应该少用public
拥有public 可变成员对象的通常是非线程安全的
一个类如果有public static final 类型的array或者返回一个类似的变量,将会是个错误
总结
你应该尽可能的减少程序元素的访问级别,小心的设计最小的public API,防止任何游离的类,接口或成员成为API的一部分,除了public static final变量外,确保类中没有其他public成员.保证public static final的对象引用是不可变类.
16. 在public类中,使用访问器方法而不是公共字段
如果一个类在包外能访问,提供访问器方法
对于包私有或者私有内部类而言,暴露成员并没有什么不对
总结
公共类永远不要暴露内部可变成员,对于不可变的类危害较小,不过仍然值得怀疑,然而,有些时候我们希望包私有或私有内部类暴露字段,无论它们是不是可变的.
17. 最小化可变性
创建不可变类的五条规则
不要提供setter方法
保证类不被拓展
使所有属性为final
使所有属性为private
确保独占访问任何可变组件
不可变类是简单的.
不可变类都是线程安全的,他们不需要同步,可以被共享
不仅仅是你可以共享不可变类,类内部一样可以共享不可变类
不可变类是其他类的重要组成部分
不可变类不会产生原子性错误
不可变类的缺点是他们要求每个不同值都需要一个单独的对象
总结
不要为每个getter写setter,除非有很好的理由不然尽量使类不可变,如果一个类不能写成不可变类,尽量限制它的可变性,你应该下意识的把每个属性写成private final 除非你有其他更好的理由.构造器在创建对象时应该为每个不变量赋值
18.使用组合代替继承
不同于方法的调用,继承违反了封装
继承的缺点
子类需要重写适合自己的方法
父类新增的类,可能会破坏子类的稳定性
只适合用在父类和子类是is-a的关系中
组合的优点
- 我们只要组合一个接口,就能把所有继承自该接口的类组合进来,虽然需要重写转发API,但是每个接口只要写一次,而且像Guava还提供了转发类.(装饰者模式)
- 继承还导致父类的缺陷再子类继承,组合可以通过转发重写设计API避免缺陷
19. 设计继承并编写文档,否则就不要使用继承
类必须为自用型的可以重写的方法提供文档,自用型即内部调用自己的函数,必须说明哪个函数会被调用,次序,以及每次的结果对后面过程的影响,以及在何种情况下会去调用.
类可能不得不提供一个明智的protect方法作为其内部的钩子
测试继承的最好方法就是写一个子类
在你发布父类时必须写个子类去测试你的类
构造器禁止调用能被重写的方法
/** * 这是一个反例,当初始化子类是,首先调用的父类的构造方法,此时子类还没被初始化,而调用了子类未 * 初始化的方法,你可能希望程序会打印俩次sub实例,实际上第一次打印了null,你要注意到这里 * instant是final修饰的,该程序导致了final字段有了不同的状态,并且,如果overrideMe()方法 * 调用了instant中的其他方法,会抛出NPE,这里没有抛出,是因为println接受null为参数。 */ public class Super { // Broken - constructor invokes an overridable method public Super() { overrideMe(); } public void overrideMe() { } } public final class Sub extends Super { // Blank final, set by constructor private final Instant instant; Sub() { instant = Instant.now(); } // Overriding method invoked by superclass constructor @Override public void overrideMe() { System.out.println(instant); } public static void main(String[] args) { Sub sub = new Sub(); sub.overrideMe(); } }
clone()或readObject()都不能被直接或间接的调用
设计继承类需要很多的努力以及大量的限制
对于不是专门设计用来继承的类来说,最好的方法就是不去继承他们
第一种方法就是把类声明为final
第二种方法就是使用组合
总结
设计一个继承的类是比较困难,你必须在文档中说明所有自用型方法,一当你写了文档,你就必须在类的整个周期为其负责,除非是真的要用到继承,你最好把类声明为final或者保证没有可以被外部使用的构造器.
20. 使用接口而不是抽象类
- 已经存在的类可以容易被改造通过实现一个新的接口
- 接口是定义mixin的理想选择,mixin是一种可以附加到主类上的一种可选的附加类型,比如Comparable
- 接口可以用于搭建非继承类型的框架
- 通过包装类的方法(装饰者模式),接口能够实现安全,强大的功能增强
- 拓展一个框架的实现使得大部分工作脱离了接口的显示,这就是模板方法模式, 这种框架通常被叫做抽象接口
- 在框架的实现中,良好的文档必不可少
21. 为后代设计接口
- 编写一个默认方法来维持 所有能想到的类的不变量是不可行的
- 当接口增加了默认方法时,已存在的接口实现类在编译时可能不会报错,但在运行时却失败了
- 虽然默认方法是新加入的特性,但它在接口的设计仍然十分重要
- 虽然在接口发布后还是有机会修正他,但你不能指望这个
22. 只使用接口去定义类型
常量接口是一种不好的接口使用方式
总结:
接口只应被用于定义类型,不应该被用来暴露常量
23. 使用类的继承而不是标记类
- 简而言之,标记类是冗长的,容易发生错误的,和低效的.
- 标记类,是类继承的低劣模仿
可能有人不知道啥是标记类,标记类就是通过传入一个flag,在类中通过判断flag的数值,实现类的不同状态
总结:
别使用标记类
24. 尽力使用静态成员类
内部类的四种类型
静态内部类
非静态内部类
匿名类
局部类
静态内部类的常见使用方法是作为一个public 的帮助类,只有和外部类一起时,才有用 ,比如 Calculator.Operation.PLUS
每一个非静态成员类都有一个隐含的指向它外部类的引用
非静态成员类的一个常见的使用方式是作为一个适配器
如果你的成员类不需要访问外部类实例,总是把他定义为static
私用的静态内部成员的常见用法是作为外部类的一个组件对象
在lambda表达式出行前,匿名类通常作为一个快速处理对象的小功能对象,现在匿名类有了另一个作用就是实现静态工厂方法
总结
如果嵌套类需要在单个方法外部可见,或者太长而无法舒适地容纳在方法内部,请使用成员类。如果成员实例需要有外部类的引用,则声明为nonstatic,否则声明为static,假设该类属于一种方法,则如果您只需要从一个位置创建实例,并且已有一个预先存在的类型可以表征该类,请将其设为匿名类,否则,将它声明为局部类.
25. 将源文件限制为单个顶级类
- 你要是非要放俩个类把类声明为private static是一种替代方案
- 如果类是顶级类的从属关系那么就放,用private static 这样可读性更好
- 最好永远不要把多个顶级的类或接口放在一个源文件下