Effective java读书笔记(2)

本文深入探讨Java中equals、hashCode、toString、clone及Comparable接口的实现原则与最佳实践,通过具体示例讲解如何正确覆盖这些方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第二章节

这一章关注的是所有对象的通用方法,并且指出了覆盖这些方法所需要的约定和细节

8)覆盖equals时请遵守通用约定

equals约定

我们首先需要我们覆盖equals时所期望的结果:
1. 类的每个实例本质上都是唯一的。
2. 不关心类是否提供了“逻辑相等”的测试功能。
3. 父类已经覆盖了equals,从父类继承过来的行为对于子类也是合适的。
4. 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用。

我们根据上面这四个期望结果,大概可以推导出什么情况下需要覆盖equals

  1. 父类并没有覆盖equals
  2. 这个类具有自己特有的“逻辑相等”的概念

我们构想了一个坐标类,符合上面这两条需要覆盖equals的条件。

//com.insanus.Coordinate.java
package com.insanus;
public class Coordinate {
    private int x;
    private int y;
    private int z;

    public static class CoordinateBuilder {
        private int x;
        private int y;
        private int z;

        CoordinateBuilder(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Coordinate getInstance() {
            return new Coordinate(this);
        }
    }

    private Coordinate(CoordinateBuilder i) {
        this.x = i.x;
        this.y = i.y;
        this.z = i.z;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getZ() {
        return z;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof Coordinate)||obj == null) {
            return false;
        } else {
            return x == ((Coordinate) obj).getX() && y == ((Coordinate) obj).getY() && z == ((Coordinate) obj).getZ();
        }
    }

    public static void main(String[] args) {
        Coordinate t1 = new CoordinateBuilder(20, 21, 22).getInstance();
        Coordinate t2 = new CoordinateBuilder(20, 21, 22).getInstance();
        Coordinate t3 = new CoordinateBuilder(20, 21, 22).getInstance();
        //等价关系
        System.out.println("对称性:" + (t1.equals(t2) && t2.equals(t1)));
        System.out.println("自反性:" + (t1.equals(t1)));
        System.out.println("非空性:" + (!t1.equals(null)));
        System.out.format("传递性: 如果存在t1==t2(%s)且t1==t3(%s),则有t2==t3(%s)\n", t1.equals(t2), t1.equals(t3), t2.equals(t3));
    }
}/* Output:
对称性:true
自反性:true
非空性:true
传递性: 如果存在t1==t2(true)且t1==t3(true),则有t2==t3(true)
*///:~

我们设计了一个构建器方便日后的扩展,并且按照约定,满足了通用约定

实现高质量equals方法的诀窍

我们并由此得出了实现高质量equals方法的诀窍:
1. 使用==操作符检查“参数是否为这个对象的引用”
2. 使用instance操作符检查“参数是否为正确的类型”
3. 把参数转换成正确的类型
4. 对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。
5. 当你编写完成equals方法之后,应该问自己三个问题:它是否是对称,传递的,一致的?
* 覆盖equals时总要覆盖hashCode
* 不要企图让equals方法过于智能
* 不要将equals声明中的Object对象替换成其他的类型

9)覆盖equals时总要覆盖hashCode

我们先看看Object对hashCode的约定

hashCode

hashCode的约定

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能

根据这几条约定,我们为刚才的Coordinate覆盖了hashCode。
我们先添加了一个属性
private volatile int result

volatile延后定义

@Override
public int hashCode() {
    if (result == 0) {
        result = 17;
        result = 31 * result + x;
        result = 31 * result + y;
        result = 31 * result + z;
    }
    return result;
}

编写这种散列函数是个研究课题,最好留给数学家和理论方面的计算机科学家。日常使用利用这种方法就足够了。

10)始终要覆盖toString

我们来看看Object源码中是如何实现toString

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

很明显,我们需要覆盖toString以便用户能够更好理解。
我们为Coordinate覆盖了toString

@Override
public String toString() {
        return String.format("x: %d, y: %d, z: %d", x, y, z);
}

11)谨慎地覆盖clone

首先我们来聊聊Cloneable这个接口,Cloneable接口的目的是作为对象的一个minxi接口。如果一个类没有实现该接口,就会返回CloneNotSupportedException异常

clone通用约定

创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,表达式:
x.clone() != x
为 true,表达式:
x.clone().getClass() == x.getClass()
也为 true,但这些并非必须要满足的要求。一般情况下:
x.clone().equals(x)
为 true,但这并非必须要满足的要求

而clone的过程是没有调用构造器,换句话说,clone方法对于只是对于域的软拷贝。clone又是递归的调用父类的clone,所以你要设计一个良好的clone方法时,你还要确保父类都拥有一个设计良好的clone。

更好的解决办法

我们可以提供一个拷贝构造器
public Yum(Yum yum)
或者是拷贝工厂
public static Yum newInstance(Yum yum);

12)考虑实现Comparable接口

Comparable
这个接口的唯一方法是compareTo,而实现了Comparable接口的对象数组排序只需要如此Arrays.sort(a)
compareTo和equals的约定都相差无几,这里举出几个不同点:
1. 无需进行类型检查
2. compareTo比较是顺序比较,不是等同性比较

compareTo约定

比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

实现类必须确保对于所有的 x 和 y 都存在 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 的关系。(这意味着如果 y.compareTo(x) 抛出一个异常,则 x.compareTo(y) 也要抛出一个异常。)

实现类还必须确保关系是可传递的:(x.compareTo(y)>0 && y.compareTo(z)>0) 意味着 x.compareTo(z)>0。

最后,实现者必须确保 x.compareTo(y)==0 意味着对于所有的 z,都存在 sgn(x.compareTo(z)) == sgn(y.compareTo(z))。 强烈推荐 (x.compareTo(y)==0) == (x.equals(y)) 这种做法,但并不是 严格要求这样做。一般来说,任何实现 Comparable 接口和违背此条件的类都应该清楚地指出这一事实。推荐如此阐述:“注意:此类具有与 equals 不一致的自然排序。”

在前面的描述中,符号 sgn(expression) 指定 signum 数学函数,该函数根据 expression 的值是负数、零还是正数,分别返回 -1、0 或 1 中的一个值。

我们给出改写过的Coordinate

//com.insanus.Coordinate.java
package com.insanus;
public class Coordinate implements Comparable<Coordinate>{
    private int x;
    private int y;
    private int z;

    public static class CoordinateBuilder {
        private int x;
        private int y;
        private int z;

        CoordinateBuilder(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Coordinate getInstance() {
            return new Coordinate(this);
        }
    }

    private Coordinate(CoordinateBuilder i) {
        this.x = i.x;
        this.y = i.y;
        this.z = i.z;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getZ() {
        return z;
    }

    public double getModel() {
        return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));
    }
    @Override
    public int compareTo(Coordinate o) {
        return Double.compare(getModel(), o.getModel());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof Coordinate)||obj == null) {
            return false;
        } else {
            return x == ((Coordinate) obj).getX() && y == ((Coordinate) obj).getY() && z == ((Coordinate) obj).getZ();
        }
    }

    public static void main(String[] args) {
        Coordinate a = new CoordinateBuilder(12, 13, 14).getInstance();
        Coordinate b = new CoordinateBuilder(12, 13, 14).getInstance();
        System.out.format("等价关系测试\n自反性(%s)\n存在a.equals(b)为true时,a.compareTo(b) == 0 (%s)\n", a.compareTo(a) == 0, (a.equals(b) == true) == (a.compareTo(b) == 0));
        //a.compareTo(null);
        //抛出NullPointerException
    }
}/* Output:
等价关系测试
自反性(true)
存在a.equals(b)为true时,a.compareTo(b) == 0 (true)
*///:~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值