大型连续剧之Effective Java总结(三)(CHAPTER 4)

193 阅读8分钟

4.类和接口

15. 最小化类和成员的访问权限

  1. 尽量让每个类或成员无法访问

  2. 四种访问级别

  3. private -- 只有声明该成员的类本身能访问

  4. package-private -- 包内的类都可以访问,默认访问级别

  5. protected -- 子类和包内类可以访问

  6. public -- 谁都可以访问

  7. 公共类的成员应该少用public

  8. 拥有public 可变成员对象的通常是非线程安全的

  9. 一个类如果有public static final 类型的array或者返回一个类似的变量,将会是个错误

总结

你应该尽可能的减少程序元素的访问级别,小心的设计最小的public API,防止任何游离的类,接口或成员成为API的一部分,除了public static final变量外,确保类中没有其他public成员.保证public static final的对象引用是不可变类.

16. 在public类中,使用访问器方法而不是公共字段

  1. 如果一个类在包外能访问,提供访问器方法

  2. 对于包私有或者私有内部类而言,暴露成员并没有什么不对

总结

公共类永远不要暴露内部可变成员,对于不可变的类危害较小,不过仍然值得怀疑,然而,有些时候我们希望包私有或私有内部类暴露字段,无论它们是不是可变的.

17. 最小化可变性

  1. 创建不可变类的五条规则

  2. 不要提供setter方法

  3. 保证类不被拓展

  4. 使所有属性为final

  5. 使所有属性为private

  6. 确保独占访问任何可变组件

不可变类是简单的.

不可变类都是线程安全的,他们不需要同步,可以被共享

不仅仅是你可以共享不可变类,类内部一样可以共享不可变类

不可变类是其他类的重要组成部分

不可变类不会产生原子性错误

不可变类的缺点是他们要求每个不同值都需要一个单独的对象

总结

不要为每个getter写setter,除非有很好的理由不然尽量使类不可变,如果一个类不能写成不可变类,尽量限制它的可变性,你应该下意识的把每个属性写成private final 除非你有其他更好的理由.构造器在创建对象时应该为每个不变量赋值

18.使用组合代替继承

  1. 不同于方法的调用,继承违反了封装

继承的缺点

  1. 子类需要重写适合自己的方法

  2. 父类新增的类,可能会破坏子类的稳定性

  3. 只适合用在父类和子类是is-a的关系中

组合的优点

  1. 我们只要组合一个接口,就能把所有继承自该接口的类组合进来,虽然需要重写转发API,但是每个接口只要写一次,而且像Guava还提供了转发类.(装饰者模式)
  2. 继承还导致父类的缺陷再子类继承,组合可以通过转发重写设计API避免缺陷

19. 设计继承并编写文档,否则就不要使用继承

  1. 类必须为自用型的可以重写的方法提供文档,自用型即内部调用自己的函数,必须说明哪个函数会被调用,次序,以及每次的结果对后面过程的影响,以及在何种情况下会去调用.

  2. 类可能不得不提供一个明智的protect方法作为其内部的钩子

  3. 测试继承的最好方法就是写一个子类

  4. 在你发布父类时必须写个子类去测试你的类

  5. 构造器禁止调用能被重写的方法

/**
  * 这是一个反例,当初始化子类是,首先调用的父类的构造方法,此时子类还没被初始化,而调用了子类未	
  * 初始化的方法,你可能希望程序会打印俩次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();
    }
}
  1. clone()或readObject()都不能被直接或间接的调用

  2. 设计继承类需要很多的努力以及大量的限制

  3. 对于不是专门设计用来继承的类来说,最好的方法就是不去继承他们

  4. 第一种方法就是把类声明为final

  5. 第二种方法就是使用组合

总结

设计一个继承的类是比较困难,你必须在文档中说明所有自用型方法,一当你写了文档,你就必须在类的整个周期为其负责,除非是真的要用到继承,你最好把类声明为final或者保证没有可以被外部使用的构造器.

20. 使用接口而不是抽象类

  1. 已经存在的类可以容易被改造通过实现一个新的接口
  2. 接口是定义mixin的理想选择,mixin是一种可以附加到主类上的一种可选的附加类型,比如Comparable
  3. 接口可以用于搭建非继承类型的框架
  4. 通过包装类的方法(装饰者模式),接口能够实现安全,强大的功能增强
  5. 拓展一个框架的实现使得大部分工作脱离了接口的显示,这就是模板方法模式, 这种框架通常被叫做抽象接口
  6. 在框架的实现中,良好的文档必不可少

21. 为后代设计接口

  1. 编写一个默认方法来维持 所有能想到的类的不变量是不可行的
  2. 当接口增加了默认方法时,已存在的接口实现类在编译时可能不会报错,但在运行时却失败了
  3. 虽然默认方法是新加入的特性,但它在接口的设计仍然十分重要
  4. 虽然在接口发布后还是有机会修正他,但你不能指望这个

22. 只使用接口去定义类型

  1. 常量接口是一种不好的接口使用方式

总结:

​ 接口只应被用于定义类型,不应该被用来暴露常量

23. 使用类的继承而不是标记类

  1. 简而言之,标记类是冗长的,容易发生错误的,和低效的.
  2. 标记类,是类继承的低劣模仿

可能有人不知道啥是标记类,标记类就是通过传入一个flag,在类中通过判断flag的数值,实现类的不同状态

总结:

别使用标记类

24. 尽力使用静态成员类

  1. 内部类的四种类型

  2. 静态内部类

  3. 非静态内部类

  4. 匿名类

  5. 局部类

静态内部类的常见使用方法是作为一个public 的帮助类,只有和外部类一起时,才有用 ,比如 Calculator.Operation.PLUS

每一个非静态成员类都有一个隐含的指向它外部类的引用

非静态成员类的一个常见的使用方式是作为一个适配器

如果你的成员类不需要访问外部类实例,总是把他定义为static

私用的静态内部成员的常见用法是作为外部类的一个组件对象

在lambda表达式出行前,匿名类通常作为一个快速处理对象的小功能对象,现在匿名类有了另一个作用就是实现静态工厂方法

总结

如果嵌套类需要在单个方法外部可见,或者太长而无法舒适地容纳在方法内部,请使用成员类。如果成员实例需要有外部类的引用,则声明为nonstatic,否则声明为static,假设该类属于一种方法,则如果您只需要从一个位置创建实例,并且已有一个预先存在的类型可以表征该类,请将其设为匿名类,否则,将它声明为局部类.

25. 将源文件限制为单个顶级类

  1. 你要是非要放俩个类把类声明为private static是一种替代方案
  2. 如果类是顶级类的从属关系那么就放,用private static 这样可读性更好
  3. 最好永远不要把多个顶级的类或接口放在一个源文件下