13.使类和成员的可访问性最小化

  • 尽可能地使每个类或者成员不被外界访问, 换句话说, 应该使用与你正在编写的软件的对应功能相一致的, 尽可能小的访问级别
  • 如果方法覆盖了超类中的一个方法, 子类中的访问级别就不允许低于超类中的访问级别, 这样可以确保任何可使用超类实例的地方都可以使用子类的实例
  • 如果一个类实现了一个接口, 那么接口中所有的类方法在这个类中都必须声明为公有的, 接口中的所有方法都隐含着公有访问级别
  • 实例域决不能是公有的

一旦这个域称为公有的, 就放弃了对存储在域中的值进行限制的能力.

  • 静态域也不能是公有的, 除开一种例外情况, 可以通过公有的静态final域来暴露常量
  • 类具有公有的静态final数组域, 或者返回这种域的访问方法, 这几乎总是错误的

14.在公有类中使用访问方法而非公有域

  • 如果类可以在它所在的包的外部进行访问, 就提供访问方法, 以保留将来改变该类的内部表示法的灵活性

15.使可变性最小化

使类称为不可变要遵循的规则:

  • 不要提供任何会修改对象状态的方法
  • 保证类不会被拓展

这样可以防止粗心或者恶意的子类假装对象的状态已经改变, 从而破坏该类的不可变行为, 为了防止子类化, 一般做法是使这个类成为final的; 另一种做法是使类的所有的构造器变成私有的或包级私有的, 并添加公有的静态工厂来代替公有的构造器

  • 使所有的域都是final的
  • 使所有的域都成为私有的
  • 确保对于任何可变组件的互斥访问

示例代码: 这个类表示一个复数, 是一个不可变类
注意代码中提供了加减乘除的运算, 但这些算数运算时创建并返回新的Complex实例, 而不是修改这个实例, 大多数重要的不可变类都使用了这种模式, 它被称为函数的做法, 因为这些方法返回了一个函数的结果, 这些函数对操作数进行运算但并不修改它

  1. /**
  2. * @author gavin
  3. * @date 2020-07-07
  4. */
  5. public class Complex {
  6. private final double re;
  7. private final double im;
  8. public Complex(double re, double im) {
  9. this.re = re;
  10. this.im = im;
  11. }
  12. public double realPart() {
  13. return re;
  14. }
  15. public double imaginaryPart() {
  16. return im;
  17. }
  18. public Complex add(Complex c) {
  19. return new Complex(re + c.re, im + c.im);
  20. }
  21. public Complex subtract(Complex c) {
  22. return new Complex(re - c.re, im - c.im);
  23. }
  24. public Complex multiply(Complex c) {
  25. return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
  26. }
  27. public Complex divide(Complex c) {
  28. double tmp = c.re * c.re + c.im * c.im;
  29. return new Complex((re * c.re + im * c.im) / tmp, (im * c.re - re * c.im) / tmp);
  30. }
  31. @Override
  32. public boolean equals(Object o) {
  33. if (this == o) {
  34. return true;
  35. }
  36. if (o == null || getClass() != o.getClass()) {
  37. return false;
  38. }
  39. Complex complex = (Complex) o;
  40. return Double.compare(complex.re, re) == 0 &&
  41. Double.compare(complex.im, im) == 0;
  42. }
  43. @Override
  44. public int hashCode() {
  45. return Objects.hash(re, im);
  46. }
  47. @Override
  48. public String toString() {
  49. return "Complex{" +
  50. "re=" + re +
  51. ", im=" + im +
  52. '}';
  53. }
  54. }

不可变对象的好处:

  • 简单
  • 线程安全, 不要求同步, 可以被自由的共享(鼓励客户端尽可能重用现有的实例)

代码示例:

  1. public static final Complex ZERO = new Complex(0, 0);
  2. public static final Complex ONE = new Complex(1, 0);
  • 不可变的类可以提供一些静态工厂, 把频繁请求的实例缓存起来

比如基本类型的包装类和BigInteger都有这样的工厂.

  • 不仅可以共享不可变对象, 甚至也可以共享他们的内部信息
  • 不可变对象为其他对象提供了大量的构件

缺点是对于每个不同的值都需要一个单独的对象.

16.复合优先于继承

  • 继承打破了封装性, 换句话说, 子类依赖于其超类中特定功能的实现细节. 超类的实现可能随版本的不同而发生变化, 子类可能会遭到破坏
  • 不拓展现有的类, 而是在新类中增加一个私有域, 它引用现有类的一个实例, 这种设计被称作”复合”, 现有的类变成新类中的一个组件. 新类中的每个方法, 都可以调用现有类实例中对应的方法, 这被称为”转发”, 新类中的方法被称为转发方法, 这样得到的类将非常稳固, 它不依赖于现有类的实现细节
  • 只有当子类真正是超类的子类型时, 才适合用继承, 换句话说, 对于两个类A和B, 只有两者之间确实存在”is-a”的关系的时候(每个A确实是B吗?), 类A才应该拓展类B
  • 如果在适合使用复合的地方使用了继承, 则会不必要的暴露实现细节, 这样得到的API会把你限制在原始的实现上, 永远限制了类的性能.
  • 继承机制会把超类API中的所有缺陷传播到子类中, 而复合则允许设计新的API来隐藏这些缺陷

17.要么为继承而设计, 并提供文档说明, 要么就禁止继承

  • 为了继承而设计的类, 需要具备良好的文档说明, 文档需精确的描述覆盖每个方法所带来的影响, 换句话说, 该类必须有文档说明它可覆盖的方法的自用性
  • 对于为了继承而设计的类, 唯一的测试方法就是编写子类 ,通常3个子类就可以测试一个可拓展的类

18.接口优于抽象类

  • 现有的类可以很容易被更新, 以实现新的接口, 如果这些方法不存在, 所需做的知识新增必须的方法. 但由于Java只能单继承, 一般来说, 无法更新现有的类来拓展新的抽象类, 如果希望两个类拓展同一个抽象类, 则必须把抽象类放到类型层次的高处, 以便这个两个类的一个祖先称为它的子类
  • 接口使定义mixin(混合类型)的理想选择

mixin是指类除了实现它的基本类型之外, 还可以实现这个mixin类型, 以表明它提供了某些可供选择的行为. 比如Comparable是一个mixin接口.

  • 接口允许我们构造非层次结构的类型框架
  • 接口使得安全的增强类的功能成为可能(包装类, 装饰者模式)
  • 接口定义类型, 对每个重要接口提供一个抽象的骨架实现, 能结合接口和抽象类的优点

骨架实现的编写: 认真研究接口, 确定哪些方法是最为基本的, 其他方法则可以根据他们来实现, 这些基本方法将成为骨架实现类中的抽象方法, 然后必须为接口中其他的方法提供具体的实现.

19.接口只用于定义类型

  • 常量接口模式是对接口的不良使用

20.类层次优先于标签类

21.用函数对象表示策略

没看懂这节, 暂时略过

22.优先考虑静态成员类

嵌套类是指被定义在另一个类内部的类. 嵌套类存在的目的应该只是为它的外围类提供服务.

嵌套类有四种(除了第一种, 其余三种都被称为内部类):

  • 静态成员类

1.可以访问外围类的所有成员, 包括那些声明为私有的成员.
2.静态成员类是外围类的一个静态成员, 与其他的静态成员一样, 遵守一样的可访问性规则, 若被声明为私有的, 就只能在外围类的内部才可以访问.
3.如果声明成员类不要求访问外围实例, 就要始终吧static修饰符放在它的声明中, 使它成为静态成员类, 而不是非静态成员类, 如果省略了static修饰符, 则每个实例都将包含一个额外的指向外围对象的引用. 保存这份引用需要消耗时间和空间, 并且会导致外围实例在符合垃圾回收时却仍然得到保留. 如果在没有外围实例的情况下, 也需要分配实例, 就不能使用非静态成员类, 因为非静态成员类的实例必须要有一个外围实例.
4.私有静态成员类的一种常见用法是用来代表外围类所代表的对象的组件, 如Map的Entry.

  • 非静态成员类

1.非静态成员类的每个实例都隐含着与外围类的一个外围实例相关联;
2.在非静态成员类的实例方法内部, 可以调用外围实例上的方法, 或者利用修饰过的this获得外围实例的引用
3.如果嵌套类的实例可以在它外围类的实例之外独立存在, 这个嵌套类就必须是静态成员类; 在没有外围实例的情况下, 要想创建非静态成员类的实例是不可能的;
4.非静态成员类的一个常见用法是定义一个Adapter, 它允许外部类的实例被看作是另一个不相关的类的实例

  • 匿名类

1.匿名类的一种常见用法是动态的创建函数对象;
2.匿名类的另一种常见用法是创建过程对象, 比如Runnable, Thread或者TimerTask实例;
3.第三种常见用法是在静态工厂方法的内部

  • 局部类

总结:
如果一个嵌套类需要在单个方法之外仍然是可见的, 或者它太长了, 不适合于放在方法内部, 就应该使用成员类
如果成员类的每个实例都需要一个指向外围实例的引用, 就要把成员类做成非静态的, 否则, 就做成静态的
假设这个嵌套类属于一个方法的内部, 如果你只需要在一个方法创建实例, 并且已经有了一个预置的类型可以说明这个类的特征, 就要把它做成匿名类, 否则, 就做成局部类