Effective Java 读书笔记(二):对于所有对象都通用的方法

来源:互联网 发布:sql去重 编辑:程序博客网 时间:2024/06/05 08:36

  • Effective Java 读书笔记二对于所有对象都通用的方法
    • 覆盖 equals 时要遵守的约定
    • 覆盖 equals 的时候总是覆盖 hashCode
    • 始终要覆盖 toString
    • 谨慎覆盖 clone
    • 考虑实现 Comparable 接口

Effective Java 读书笔记(二):对于所有对象都通用的方法

尽管 Object 是一个具体的类,但设计它主要是为了扩展,它的所有非 final 方法(equals、hashCode、toString、clone)都有明确的通用约定。任何一个类,在覆盖这些方法的时候都要遵守通用约定。如果违反约定,其他依赖这些约定的类(比如 HashMap、HashSet)就无法结合该类一起正常工作。

覆盖 equals 时要遵守的约定

有一些情况,是不需要覆盖的:
1. 如果不覆盖 equals,那么类的每个实例都是唯一的。对于代表活动实体而非值的类来说就是如此,比如 Thread。
2. 不关心类是否提供“逻辑相等”的测试功能。
3. 超类提供了 equals,子类从超类继承过来的行为对子类是合适的。
4. 类是私有的或包级私有的,可以确定它的 equals 方法不会被调用。这种应该可以覆盖 equals 方法,以防被意外调用:

public boolean equals(Object obj) {    throw new RuntimeException("equals not support");}

那么什么时候应该覆盖 equals 呢?类有自己的“逻辑相等”的概念,而且超类 equals 方法不满足条件,这通常就是值类,比如 Integer、Date。覆盖 equals 时必须遵循一些通用约定:
1. 自反性
2. 对称性
3. 传递性
4. 一致性

实现 equals 方法时的一些诀窍:
1. 先判 null,null 则返回 false。
2. 使用 == 检查是否为同一个对象的引用,是则返回 true。
3. 使用 instanceof 检查是否为正确的类型,否则 false。

剩下的就是对比每个关键域了。

覆盖 equals 的时候总是覆盖 hashCode

如果覆盖了 equals 但是没有覆盖 hashCode,就会违反通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,这样的集合包含 HashMap、HashSet等。约定如下:
1. 只要 equals 方法比较所需要的信息没有修改,那么 hashCode 方法的返回值也不能变。
2. 两个对象根据 equals 判定为相等,那么其 hashCode 也必须相等。
3. 两个对象根据 equals 判定为不相等,hashCode 可以不同,也可以相同。不同的话,能够提高散列表的性能。

始终要覆盖 toString

主要用于打日志调试,如果对性能要求不高,可以直接输出 Json 串。

谨慎覆盖 clone

所有实现了 Cloneable 接口的类,都应该用一个公有方法覆盖 clone。此方法要首先调用 super.clone() ,如果就此结束,那就只是“浅拷贝”了。如果需要“深拷贝”,则需要手动拷贝任何包含内部“深层结构”的可变对象。下面我们给一个示例:

public class Pojo implements Cloneable {    private String str;    private List<Integer> intList;    private int i;    @Override    public Pojo clone() throws CloneNotSupportedException {        return (Pojo)super.clone();    }}

在 JDK1.5 中引入了协变返回类型作为泛型,覆盖方法的返回类型可以是被覆盖方法的子类了。由于 Object 的 clone 方法返回的是 Object,所以 Pojo 的 clone 方法内部进行了显式类型转换。这体现了一个原则:永远不要让客户去做任何类库能够替客户完成的事情。上面这个示例依然是浅拷贝的,因为 intList 是一个可变对象,默认情况拷贝的只是其引用。其深拷贝版本如下:

public class Pojo implements Cloneable {    private String str;    private List<Integer> intList;    private int i;    @Override    public Pojo clone() throws CloneNotSupportedException {        Pojo clone = (Pojo) super.clone();        List<Integer> dstList = new ArrayList<>(intList);        clone.setIntList(dstList);        return clone;    }    public void setIntList(List<Integer> intList) {        this.intList = intList;    }}

这里 List 中元素是 Integer 类型,Integer 是不可变的,否则还需要额外对每个元素做一次拷贝。

还有一个问题:clone 架构与引用可变架构的 final 域正常用法不相兼容。比如,上面的 intList 如果声明为 final,是没办法做深度拷贝的。

在实际项目中,推荐使用拷贝构造方法或静态工厂来代替 clone,因为这样更灵活,也没有 clone 的那些约束,不与 final 冲突。

考虑实现 Comparable 接口

如果一个值类具有明显的内在排序关系,你就应该考虑实现 Comparable 接口:

public interface Comparable<T> {    public int compareTo(T o);}

一旦实现了 Comparable 接口,就可以跟很多泛型算法以及依赖于该接口的集合实现进行协作,比如:

Arrays.sort(a);

compareTo 方法要遵守的通用约定和 equals 方法类似。对于 Collection、Map 或 Set 的通用约定是按照 equals 来定义的,而有序集合 TreeMap、TreeSet 是按照 Comparable 来定义的。所以应该尽量保证两个对象 equals 返回 true 时,compateTo 返回 0,否则有序集合就无法遵守集合接口的通用约定了。

阅读全文
1 0