美文网首页
Effective Java 2 读书笔记 78条

Effective Java 2 读书笔记 78条

作者: 没有颜色的菜 | 来源:发表于2018-11-04 19:40 被阅读0次

    第一条 考虑使用静态工厂方法代替构造器

    优势

    1. 有名称
    2. 不必每次调用的时候都创建一个对象
    3. 返回原返回类型的所有子类型的对象
    4. 在创建参数实例化的时候,他们使代码变得更简洁

    缺点

    1. 类如果不含有共有的或者受保护的类构造器,就不能被子类化
    2. 他们与其他的静态方法没什么区别

    第二条 遇到多个构造器时要考虑用构建器

    构造器参数多,个数多,则优先使用 Builder 模式,构建器比JavaBeans更加安全

    public interface Builder<T> {
      public T build();
    }
    

    第三条 用私有构造器火鹤枚举类型强化singleton属性

    单元素的枚举类型已经成为实现Singleton的最佳方法

    public enum Elvis {
      INSTANCE;
      public void leaveTheBuilding() {}
    }
    

    第四条 通过私有构造器强化不可实例化的能力

    超类也不能被实例化

    public class UtilityClass {
      private UtilityClass() {
        throw new AssertionError();
      }
    }
    

    第五条 避免创建不必要的对象

    1. 避免重复创建相同的对象
    2. 优先使用基本类型而不是装箱基本类型
    3. 不要盲目觉得创建对象代价十分昂贵,使用对象池的特例:数据库连接池,否则没必要创建对象池,会影响GC

    第六条 消除过期的对象引用

    1. 常见的场景在与ArrayList,删除对象后需要把该索引位置置空。
    2. 监听器,其他回调

    可使用 WeakHashMap 将键保持为弱引用

    第七条 避免使用终结方法

    终结方法通常是不可预测的,也是很危险的吗,一般情况下是不必要的。使用终结方法是很危险的。

    终结方法线程的优先级比应用程序线程的优先级低很多

    // 不保证 finalize 执行
    System.gc System.runFinaliaation
    // 保证执行
    System.runFinalizersOnExit Runtime.runFinalizersOnExit
    

    第八条 覆盖 equals 时请遵守通用约定

    • 类是私有的或是包级私有的,可以确定它的 equals 方法永远不会被调用。
      防止方法被调用的方法
       @Ovveride
       public boolean equals(Object o) {
         throw new AssertionError();
       }
      
    equals 方法实现了等价关系
    • 自反性
    • 对称性
    • 传递性
    • 一致性
      一般做法
    @Override
    public boolean equals(Object o) {
      if (o == this) {
        return true;
      }
      if (!(o instanceOf MyClass)) {
        return false;
      }
      MyClass class = (MyClass) o;
      // 比较各个域值
      ......
    }
    

    第九条 覆盖 equals 时总要覆盖 hashCode

    Object 规范(JavaSE6)

    • 同一个对象多次调用必须一致,同一个程序多次执行可以不一致
    • equals 相等,hashCode 必然要相等
    • hashCode 相同,equals 不一定相同

    一个好的散列函数通常倾向于“为不同的对象产生不相等的散列码”
    如何计算:

    1. 把某个非零常熟值,比如说17,保存到一个名为 result 的 int 类型的变量中。
    2. 对于对象中每个关键域 f (指 equals 方法中涉及到的每个域),完成以下步骤:
      a. 为该域计算 int 类型的散列码 c :
      • 如果该域是 boolean 类型,则计算 (f ? 1 : 0)
      • 如果该域是 byte ,char,short,int,则计算 (int)f
      • 如果该域是 long,则计算(int)(f ^ (f >> 32))
      • 如果该域是 float,则计算 Float.floatToIntBits(f);
      • 如果该域是 double,则计算 Double.doubleToLongBits(f), 然后按照步骤 a.3,计算 long 的散列值
      • 如果该域是一个对象引用,并且该类的 equals 方法通过递归的调用 equals 的方式来比较这个域,则同样为这个域递归的调用 hashCode。如果这个域为 null, 则为 0
      • 如果该域是一个数组,则需要把每一个元素当作单独的域来处理。

    b. 按照下面的公式,把步骤2.a 中计算得到的散列码 c 合并到 result 中:
    result = 31 * result + c;

    1. 返回 result
    2. check

    第十条 始终要覆盖 toString

    建议所有的子类都应该覆盖这个方法

    第十一条 谨慎的覆盖 clone

    创建和返回该对象的一个拷贝,不必调用构造器创建对象
    JavaSE6 的约定内容:

    • x.clone() != x 为 true
    • x.clone().getClass() == x.getClass() 为 true
    • x.clone().equals(x) 为 true
      一般写法,通过递归 super.clone() 创建对象,再将域逐个复制即可

    第十二条 考虑实现 Comparable 接口

    实现了 Comparable 接口,就表明它的实例具有内在的排序关系。
    当然,也可以使用外在排序 Comparator

    第十三条 使类和成员的可访问性最小化

    设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰的隔离开来。

    • 尽可能地使每个类或成员不被外界访问
    • 有四种访问性:
      • 私有的(private)
      • 包级私有的(default)
      • 受保护的(protected)
      • 共有的(public)
        实例域不能是共有的

    第十四条 在共有类中使用访问方法而非共有域

    有时候,可能会编写一些退化类,没有什么作用,只是用来集中实例域
    Java平台类库中有一些违反了,但应该警惕
    共有类永远不应该暴露可变的域

    第十五条 使可变性最小化

    不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变,Java 平台类库中包含了许多不可变类,比如 String,基本类型包装类,BigDecimal 和 BigInteger。存在不可变的类有很多理由:不可变的类比可变的类更加容易设计,实现和使用。
    原则

    • 不要提供任何会修改对象状态的方法
    • 保证类不会被扩展
    • 使所有的域都是 final 的
    • 使所有的域都是私有的
    • 确保对于任何可变组件的互斥访问

    第十六条 复合优先于继承

    继承虽然是实现代码重用的有力手段,但它会打破封装性。而复合可以解决这个问题,只有当子类是超类的子类型时,才适用继承(is-a关系)。Java 平台类库中 Stack和Vector,Properties和Hashtable 违反了这个原则

    第十七条 要么为继承而设计,并提供文档说明,要么就禁止继承

    对于专门为了继承而设计的类需要具有良好文档说明,该文档必须精确的描述覆盖每个方法所带来的影响

    • 构造器绝不能调用可被覆盖的方法

    第十八条 接口优于抽象类

    Java 程序设计语言提供了两种机制,可以用来定义允许多个实现的类型:接口和抽象

    • 现有的类很容易被更新,以实现新的借口
    • 接口是定义mixin(混合类型)的理想选择
    • 接口允许我们构造非层次结构的类型框架

    第十九条 接口只用于定义类型

    接口应该只被用来定义类型,不应该被用来导出常量
    导出常量的做法:

    public class PhysicalConstants  {
      //  私有
      private PhysicalConstants() {}
      public static final double AVOGADROS_NUMBER = 6.02214199e23;
    }
    

    第二十条 类层次优先于标签类

    标签类使得类变得复杂难懂,考虑使用类层次替换
    域是不能做成 final 的

    第二十一条 用函数对象表示策略

    简而言之,函数指针的主要用途就是实现策略模式。为了在 Java 中实现这种模式,要声明一个接口来表示该策略,并且为每个具体的策略声明实现一个该接口的类。当一个具体策略只被使用一次的时候,通常使用匿名类的方式和实例化这个具体策略类。当一个具体策略是设计用来重复使用的时候,他的类通常就要实现为私有的静态成员类,并通过共有的静态final域被导出,其类型为该策略接口。

    第二十二条 优先考虑静态成员类

    嵌套类被定义为在另外一个类的内部的类,有四种:静态成员类,非静态成员类,匿名类,局部类,后三者为内部类;

    第二十三条 请不要在新代码中使用原生态类型

    例如,List 原生态类型不能再编译器发现类型转化错误,而使用范型则可以,更加安全

    第二十四条 消除非受检警告

    第二十五条 列表优先于数组

    第二十六条 优先考虑范型

    不能创建范型数组

    第二十七条 优先考虑范型方法

    public static <T entends Comparable<T>> T max(List<T> list) {
      Iterator<T> i = list.iterator();
      T result = i.next();
      while (i.hasNext()) {
        T t = i.next();
        if (t.compareTo(result) > 0) {
          result = t'
        }
      }
    }
    
    

    第二十八条 利用有限通配符来提升API的灵活性

    第二十九条 优先考虑类型安全的异构容器

    第三十条 用 enum 代替 int

    第三十一条 用实例域代替序数

    第三十二条 用 EnumSet 代替位域

    第三十三条 用EnumMap 代替序数索引

    第三十四条 用接口模拟可伸缩的枚举

    虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。

    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; }
      }
      private final String symbol;
      BasicOperation(String symbol) {
        this.symbol = symbol;
      }
    }
    

    第三十五条 注解优先于命名模式

    命名模式指的是:给一些类或者方法使用有特定约束的名字

    第三十六条 坚持使用 Override 注解

    坚持使用 Override 注解能够避免一些错误

    第三十七条 用标记接口定义类型

    标记接口是没有包含任何方法的接口,例如 Serializable 接口,不包含任何方法

    第三十八条 检查参数的有效性

    对于共有的可以使用 throws 说明方法会产生什么异常
    对于私有的方法可以使用 assert 断言
    简而言之,每当编写方法或者构造器的时候,应该考虑它的参数有哪些限制。应该把这些限制写到文档中,并且在这个方法提开头处,通过显式的检查来实施这些限制。

    第三十九条 必要时进行保护性拷贝

    简而言之,如果类具有从客户端得到或者返回客户端的可变组件,类就必须保护性地拷贝这些组件。如果拷贝的成本受到限制,并且类信任它的客户端不会不恰当的修改组件,就可以在文档中指明客户端的职责是不得修改受到影响的组件,以此来代替保护性拷贝

    第四十条 谨慎设计方法签名

    • 谨慎的选择方法的名称
    • 不要过于追求提供便利的方法
    • 避免过长的参数列表

    第四十一条 慎用重载

    安全而保守的策略是,永远不要导出两个具有相同参数树木的重载方法

    第四十二条 慎用可变参数

    第四十三条 返回零长度的数组或者集合,而不是null

    返回类型为 List 或数组,长度为零是返回 Collections.empty();

    第四十四条 为所有导出的API元素编写文档注释

    第四十五条 将局部变量的作用域最小化

    第四十六条 for-each 循环优先于传统的for循环

    第四十七条 了解和使用类库

    第四十八条 如果需要精确的答案,请避免使用 float 和 double

    第四十九条 基本类型优先于装箱基本类型

    第五十条 如果其他类型更合适,则尽量避免使用字符串

    第五十一条 当心字符串连接的性能

    第五十二条 通过接口引用对象

    应该优先使用接口而不是类来引用对象

    第五十三条 接口优先于反射机制

    第五十四条 谨慎的使用本地方法

    第五十五条 谨慎的使用优化

    第五十六条 遵守普遍接受的命名习惯

    第五十七条 只有针对异常的情况才使用异常

    第五十八条 对可恢复的情况使用受检异常,对编程错误使用运行时异常

    第五十九条 避免不必要地使用受检的异常

    第六十条 优先使用标准的异常

    第六十一条 抛出与抽象相对应的异常

    第六十二条 每个方法抛出的异常都要有文档

    第六十三条 在细节消息中包含能捕获失败的消息

    第六十四条 努力是失败保持原子性

    第六十五条 不要忽略异常

    第六十六条 同步访问共享的可变数据

    第六十七条 避免过度同步

    第六十八条 executor 和 task 优先于线程

    第六十九条 并发工具优先于 wait 和 notify

    第七十条 线程安全性的文档化

    第七十一条 慎用延迟初始化

    第七十二条 不要依赖于线程调度器

    第七十三条 避免使用线程组

    第七十四条 谨慎地实现 Serializble 接口

    实现 Serializable 接口而付出第最大代价是,一旦一个类被发布,就大大降低了“改变这个类的实现” 的灵活性

    第七十五条 考虑使用自定义的序列化形式

    第七十六条 保护性的编写 readObject 方法

    第七十七条 对于实例控制,枚举类型优先于 readResolve

    第七十八条 考虑使用序列化代理代替序列化实例

    相关文章

      网友评论

          本文标题:Effective Java 2 读书笔记 78条

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