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

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,否则有序集合就无法遵守集合接口的通用约定了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值