美文网首页
Effective Java

Effective Java

作者: kaiker | 来源:发表于2022-12-31 18:04 被阅读0次

第二章 创建和销毁对象

1、静态工厂代替构造器

  • 避免每次都创建对象
  • 可返回任何子类型对象
  • .from .of .valueOf .create .instance .type 有不同的含义

2、遇到多个构造器参数是要考虑使用构构建器

  • 所有属性进行set,在构造过程中可能导致不一致状态出现
  • 建造者模式,属性final,加一个静态成员类
建造者

3、用私有构造器或枚举类型强化Singleton

4、通过私有构造器强化不可实例化能力

5、优先考虑依赖注入引用资源

  • 不通过单例和静态工具类来实现依赖一个或多个底层资源类
  • 将这些资源或工厂传给构造器,通过他们创建类

6、避免创建不必要的对象

  • 优先使用基本类型而不是装箱类型

7、消除过期的对象引用

  • 栈pop掉的元素不会被垃圾回收
  • 一旦对象引用已经过期,只需清空这些引用,pop的时候加个element[size] = null
过期对象引用

8、避免使用终结方法和清除方法

  • 不能保证被及时执行

9、try-with-resources优先于try-final

第三章 对于所有对象都通用的方法

10、覆盖equals时遵守通用约定

  • 自反性
  • 对称性
  • 传递性
  • 一致性
  • 非null equals null必须返回false
  • 使用==检查参数是否胃这个对象的引用
  • 使用instanceof操作检查参数是否为正确类型
  • 参数转换成正确类型
  • 对于该类中的每个关键域,检查参数中的域是否域该对象中对应的域相匹配
  • 覆盖equals时总要覆盖hashCode
  • 不要企图让equals方法过于智能
  • 不要将equals声明中的Object对象替换为其他类型,替换了就不是覆盖时重载了

11、覆盖equals时总要覆盖hashCode

  • 相等的对象必须具有相等的散列码
  • 如果一个类是不可变的,计算散列码开销大,就考虑把散列码缓存在对象内部

12、始终覆盖toString

13、谨慎地覆盖clone

  • clone,无须调用构造器就可以创建对象
  • 需要逐域clone,只super.clone有可能导致部分引用的可变域没被clone最后出错
  • clone方法不应该在构造过程中,调用可覆盖的方法

14、考虑实现Comparable接口

第四章 类和接口

15、使类和成员的可访问性最小化

16、在公有类中使用访问方法而非公有域

17、使可变性最小化

  • 不可变的类比可变类更加易于设计、实现和使用
  • 不要提供任何会修改对象状态的方法
  • 保证类不会被扩展
  • 声明所有的域都是final
  • 声明所有域都是私有
  • 确保对于任何可变组件互斥访问,如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用

18、复合优先于继承

19、要么设计继承并提供文档说明,要么禁止继承

下面的例子,super调用父类构造器,override调用子类的,但是此时还未完成初始化


继承

无论是clone还是readObject,都不可以调用可覆盖的方法,不管是直接还是间接。覆盖的方法将在子类被序列化或是修正克隆对象状态之前运行,就得不到想要的结果。

20、接口优于抽象类

  • 接口允许构造非层次类型的框架
  • 通过对接口提供一个抽象的股价实现,可以把接口和抽象类的有点结合起来。

21、为后代设计接口

  • 有缺省方法后,如果没实现也不会报错

22、接口只用于定义类型

  • 用枚举定义常量

23、类层次优于标签类

24、静态成员类优于非静态成员类

  • 静态成员类,公有类的辅助类,只有和外部类一起使用才有意义。
  • 非静态成员类,每个实例都隐含地与外围类的一个外围实例相关联。可以调用外围实例上的方法。如果声明成员类不要求访问外围实例,就要始终把修饰符static放在它的声明中。引用是会有成本的。
  • 匿名类, lambda表达式
  • 局部类

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

第五章 泛型

26、请不要使用原生态类型

  • 主要是针对容器,需要指定类型

27、消除非受检的警告

  • 非受检转换之类的

28、列表优于数组

  • 数组是协变的,类型有父子关系
  • 泛型是可变的
  • 数组和泛型不能很好地混合使用

29、优先考虑泛型

30、优先考虑泛型方法

31、利用有限制通配符来提升API的灵活性

  • 生产者用<? extends T>;消费者用<? super T>
? super T

32、谨慎并用泛型和可变参数

static void f(List<String>... stringLists) {
    List<Integer> intList = List.of(42);
    Object[] objects = stringLists;
    object[0] = intList;  // Heap pollution;
    String s = stringLists[0].get(0);
}

33、优先考虑类型安全的异构容器

  • 一个容器存储多种类型,可以把key变成 Class对象,实现异构

第6章 枚举和注解

34、用enum代替int常量

  • 枚举类型没有可以访问的构造器,所以它是真正的final类

35、用实例域代替序数

  • 给枚举加实例域而不是用自带的序数

36、用EnumSet代替位域

37、用EnumMap代替序数索引

38、用接口模拟可扩展的枚举

public interface Operation {
    double apply(double x, double y);
}

public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) {return x + y;}
    }
}

39、注解优先于明明模式

40、坚持使用Override注解

41、用标记接口定义类型

  • 没有方法的接口,只用于标明一个类实现了具有某种属性的接口

第7章 Lambda和Stream

42、Lambda优先于匿名类

  • 匿名类可以获得对自身的引用,但是lambda不行

43、方法引用优先于Lambda

44、坚持使用标准函数接口

UnaryOperator<T>
BinaryOperator<T>
Predicate<T>
Function<T,R>
Supplier<T>
Consumer<T>

45、谨慎使用Stream

  • 从代码块中可以读取或修改范围内的任意局部变量,lambda则只能读取final或有效的final变量
  • 从代码块中可以从外围方法中跳出循环
  • Stream一旦将一个值映射到某个其他值,原来的值就丢失了

46、优先选择Stream中无副作用的函数

47、Stream要优先用Collection作为返回类型

  • Collection接口是Iterabale的一个子类型,它有一个stream方法,因此提供了迭代和stream访问

48、谨慎使用Stream并行

  • 最好进行精确的子范围划分再进行处理

第8章 方法

49、检查参数的有效性

50、必要时进行保护性拷贝

51、谨慎设计方法签名

  • 参数应当四个或更少
  • 不要过于追求提供便利的方法

52、慎用重载

  • 重载方法的选择是静态的,覆盖方法的选择是动态的
  • 安全而保守的策略是永远不要导出两个具有相同参数数目的重载方法

53、慎用可变参数

54、返回零长度的数组或集合而不是null

55、谨慎返回optional

  • 永远不要通过返回Optional的方法返回null

56、为所有导出的API元素编写文档注释

第9章 通用编程

57、局部变量的作用域最小化

58、for-each循环优先于传统for循环

  • 完全隐藏迭代器或索引变量,避免了混乱和出错的可能。

59、了解和使用类库

60、如果需要精确地答案,避免使用float和double

61、基本类型优先于装箱基本类型

  • 对装箱基本类型运用==几乎总是错误的。
  • 反复装箱会导致明显的性能下降。

62、如果其他类型更合适,则尽量避免使用字符串

  • 可能造成无意的共享。

63、了解字符串连接的性能

  • 连接n个字符串而重复使用字符串连接操作,需要n的平方级时间,内容需要被拷贝。
  • 使用StringBuilder代替String

64、通过接口引用对象

  • 如果没有合适的接口,就用类层次结构中提供了必要功能的最小的具体类来引用对象。

65、接口优先于反射机制

66、谨慎地使用本地方法

67、谨慎地进行优化

  • 要努力避免那些先知性能的设计决策,最主要的是API、交互层协议以及永久数据格式。
  • 要考虑API设计决策的性能后果。

68、遵守普遍接受的命名习惯

第10章 异常

69、只针对异常的情况才使用异常

  • 如果类具有状态相关的方法,即只有在特定的不可预知的条件下才可以被调用的方法,这个类往往也应该有个单独的状态测试。

70、对可恢复的情况使用受检异常,对编程错误使用运行时异常

71、避免不必要地使用受检异常

  • 消除受检异常最容易的方法是,返回所要得结果类型的一个optional

72、优先使用标准异常

73、抛出与抽象对应的异常

  • 更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。

74、每个方法抛出的所有异常都要建立文档

75、在细节消息中包含失败-捕获信息

  • 异常的细节信息应该包含对该异常有贡献的所有参数和域的值

76、努力使失败保持原子性

  • 使对象保持在被调用之前的状态
  • 获得失败原子性最常见的办法是在执行操作之前检查参数的有效性
public Object pop() {
    if (size == 0) throw new EmptyStackException();
}

77、不要忽略异常

第11章 并发

78、同步访问共享的可变数据

  • 除非读和写操作都被同步,否则无法保证同步能起作用

79、避免过度同步

  • 在一个被同步的区域内部,不要调用设计成要被覆盖的方法,或者是由客户端以函数对象形式提供的方法。
  • 应该在同步区域内做尽可能少的工作。
  • 过度同步的实际成本并不是指获取锁所花费的CPU时间,而是指失去了并行的机会。
  • 为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。

80、executor、task和stream优先于线程

  • Thread是既充当工作单元,又是执行机制。在Executor Framework中,工作单元和执行机制是分开的。工作单元,称为task。任务有两种Runnable和Callable。执行任务的通用机制是executor service。

81、并发工具优先于wait和notify

82、线程安全性的文档化

  • lock域应该始终声明为final
  • 避免拒绝服务供给,可以使用私有锁对象来代替同步方法
private final object lock = new Object();

public void foo() {
    synchronized(lock) {
    }
}

83、慎用延迟初始化

  • 在大多数情况下,正常的初始化要优先于延迟初始化
  • 如果要对静态域使用延迟初始化,就使用lazy initializaition holder class
private static class FieldHolder {
    static final FieldType field = computeFieldValue();
}

private static FieldTpe getField() {
    return FieldHolder.field;
}
  • 如果要对实例域使用延迟初始化,就使用双重检查模式
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;
    if (result == null) {
        synchronized(this) {
            if (field == null) field = result = computeFieldValue();
        }
    }
}

84、不要依赖于线程调度器

  • 如果线程没有在做有意义的工作,就不应该运行。
  • 任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能是不可移植的。

第12章 序列化

85、其他方法优先于Java序列化

  • 避免序列化供给的最佳方式是永远不要反序列化任何东西。
  • 永远不要反序列化不被信任的数据。

86、谨慎地实现Serializable接口

  • 如果没有声明一个显式的序列版本UID,兼容性将会遭到破坏。
  • 如果一个类实现了Serializable接口,它的字节流编码就变成了它的导出的API的一部分。
  • 内部类不应该实现Serializable接口。

87、考虑使用自定义的序列化形式

  • 默认的序列化形式描述了该对象内部所包含的数据以及每一个可以从这个对象到达的其他对象的内部数据。
  • 使用默认序列化形式会消耗过多空间、时间,会引起栈溢出等问题。
  • 如果在读取整个对象状态的任何其他方法上强制任何同步,则也必须在对象序列化上强制这种同步。

88、保护性地编写readObject方法

  • readObject方法实际上相当于另一个公有构造器
  • 伪造字节流可能通过反序列化实现攻击
  • 对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象。如果检查失败,则抛出一个InvalidObjectException。

89、对于实例控制,枚举类型优先于readResolve

  • 如果单例包含一个非瞬时的对象引用域,这个域的内容就可以在单例的readResolve方法运行之前被反序列化

90、考虑用序列化代理代替序列化实例

  • 为可序列化的类设计一个私有的静态嵌套类,有一个单独的构造器,其参数类型就是外围类。这个构造器只从它的参数中复制数据
private static class SerializationProxy implements Serializable {
    private final Date start;
    private final Date end;
    SerializationProxy(Period p) {
        this.start = p.start;
        this.end = p.end;
    }

    private Object writeReplace() { return new SerializationProxy(this); }

}


相关文章

网友评论

      本文标题:Effective Java

      本文链接:https://www.haomeiwen.com/subject/eagdqdtx.html