ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Effective Java--读书笔记(三)【第一版待完成】

2020-12-24 17:58:14  阅读:187  来源: 互联网

标签:instanceof obj Effective 读书笔记 Point equals Java public ColorPoint


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

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

10.1 什么情况下不用覆盖equals

  • 类的每个实例本质上都是唯一的。关注的实体本体还不是里面的值的类。例如Thread
  • 类没有必要提供“逻辑相等(logical equality)”的测试功能。例如java.util.regex.Pattern可以覆盖equals,已检查两个Pattern实例是否代表同一个正则表达式。(设计者认为程序员不需要这样的功能)
  • 超类已经覆盖了equals,超类的行为对于这个类也是合适的。例如大多数Set实现了都从AbstractSet继承equals实现。
  • 类是私有的,或者包是私有的,可以确定它的equals方法永远都不会被调用。但是如果你非要规避风险,可以覆盖equals方法,以确保它不会被意外调用:
/**
 * @author yuxingang
 * date 2020-12-24  09:29
 */
public class Equals {
    private class DonWantEqual{
        @Override
        public boolean equals(Object obj) {
            System.out.println("1111");
            throw new AssertionError("断言异常");//Method is never called
        }
    }
}

断言:对参数和环境等做出判断,避免程序因不当的输入或错误的环境而产生逻辑异常。
简单来说:断言主要使用在代码开发和测试时期,用于对某些关键数据的判断,如果这个关键数据不是你程序所预期的数据,程序就提出警告或退出。

10.2 什么情况下应该覆盖equals

如果类具有逻辑相等(logical equality)概念,而且超类还没有覆盖equals。这通常属于“值类(value class)”。值类仅仅是一个表示值的类,例如:Integer或者String。就必须覆盖equals。而且这样做也使得这个类的实例可以被用作映射表(map)的键(key),或者集合(set)的元素,使得映射或者集合表现出预期的行为。

10.2.1 特殊情况

有一种值类,不需要覆盖equals方法,即实例受控(静态工厂提供的类或者枚举类型等)确保“每个字至多只存在一个对象”的类。对于枚举类型,逻辑相同和对象等同是一回事。因此没必要重写equals。

10.2.2 重写equals规范

必须遵守以下通用约定:
equals方法实现了等价关系,其属性如下:

  • 自反性:对于任何非null的引用值x,x.equals(x)必须返回true
  • 对称性:对于任何非null的引用值x和y,如果x.equals(y)返回true,那么y.equals(x)返回true
  • 传递性:对于任何非null的引用值x,y,和z。如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)返回true
  • 一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有修改,多次调用x.equals(y)返回值必须一样
  • 对于任何非null的引用值x,x.equals(null)必须返回false。

10.3 违反equals重写规范的可能性

10.3.1 自反性

基本上不可能违反,除非你秀逗了

10.3.2 对称性

示例:如下,它实现了区分大小写的字符串。字符串由toString保存,但在equals操作中被忽略。

/**
 * @author yuxingang
 * date 2020-12-24  10:45
 */
public final class CaseInsensitiveString {
    private final String s;
    public CaseInsensitiveString(String s){
        this.s = Objects.requireNonNull(s);
    }
    //Broken - violates symmetry!
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
        if(obj instanceof String)//One-way interoperability!
            return s.equalsIgnoreCase((String)obj);
        return false;
    }
}

测试:

        CaseInsensitiveString cis = new CaseInsensitiveString("YuXinGang");
        String s = "yuxingang";
        System.out.println("正向比较cis?s===>"+ cis.equals(s));
        System.out.println("反向比较cis?s===>"+s.equals(cis));

结果:
在这里插入图片描述
结论:问题在于上面的equal方法左边的会进行大小写处理,但是右边是不会的,它只是一个参数。所以在s.equals(cis),中s本身就是小写,在进行大小写处理的时候还是小写,而右边的参数是存在大小写的,所以返回false。一旦违反了equals约定,当其它对象面对你的对象时,你完全不知道这些对象的行为会怎么样
解决方案:

    @Override
    public boolean equals(Object obj) {
        return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString)obj).s.equalsIgnoreCase(s);
    }

10.3.3 传递性

示例:假设讲一个新的值组件添加到超类中,换句话说,子类增加的信息会影响equals的比较结果。如下是一个不可变的二维整数型Point类
Point类

/**
 * @author yuxingang
 * date 2020-12-24  10:45
 */
public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Point))
            return false;
        Point p = (Point) obj;
        return p.x == y && p.y == y;
    }
}

这个时候想要扩展这个类,想加点颜色信息
ColorPoint类

/**
 * @author yuxingang
 * date 2020-12-24  11:31
 */
public class ColorPoint extends Point {
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }
}

如果完全不提供equals方法,那么颜色信息就会被忽略掉。尝试第一种在子类的equals实现

	//Broken -violates symmetry!
    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof ColorPoint))
            return false;
        return super.equals(obj)&&(((ColorPoint) obj).color)==color;
    }

问题所在:在比较普通点和有色点,以及相反的情形时,可能会得到不同的结果。普通点.equals(有色点)直接忽略了颜色信息。而相反的,由于参数类型不一致,所以永远都是false

Point p = new Point(1,2);
ColorPoint cp = new ColorPoint(1,2,ColorPoint.RED);

如上,p.equals(cp)返回true。cp.equals§返回false。我们可以在ColorPoint.equeals在进行“混合比较”时忽略颜色信息。

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Point))
            return false;
        //if obj is a normal Point,do a color-blind comparison
        if(!(obj instanceof ColorPoint))
            return super.equals(this);
        //obj is a ColorPoint; do a full comparison
        return super.equals(obj)&&((ColorPoint)obj).color==color;
    }
        ColorPoint p1 = new ColorPoint(1,2,Color.RED);
        Point p2 = new Point(1,2);
        ColorPoint p3 = new ColorPoint(1,2,Color.BLUE);

此时,p1.equals(p2)和p2.equals(p1)返回true,但是p1.equals(p3)则返回false,这虽然保证了对称性,但是违反了传递性。前两种没有考虑颜色,但是后一种把颜色算了上去。并且这还可能会导致无限递归的可能性。例如有Point有2个子类,如果各自都带有上面的方法,就会抛StackOverflowError异常。

10.3.4 一致性

如果两个对象相等,它们就必须使用保持相等,除非它们中有对象被修改了。所以先判断对象是否可变,如果不可变,那必须的保证两个对象equals方法必须相等。

10.3.5 非空性

所有的对象都不能等于null

@override
public boolean equals(Object obj){
    if(obj == null)
        return false;
    ....
}

以上if测试是不必要的。为了测试其参数的等同性,equals方法必须先把参数转化成适当的类型,以便可以调用它的方法或成员变量。在进行转化之前,equals必须使用instanceof操作符,检查其参数是否是该类的对象或子类对象。
如果漏掉了instanceof检查,并且传递给equals方法的参数又是错误类型,那么equals方法将会抛出ClassCastException异常,这就违反了equals的约定。但是,如果instanceof的第一个操作数为null,那么,不管第二个操作数是什么类型,instanceof操作符都指定应该返回false,因此不需要单独的null检查,而应该用instanceof。

10.4 实现高质量equals的诀窍

1、“使用==操作符检查“参数是否为这个对象的引用”。如果是则返回true。这是一种性能优化

if(this==obj)
    return true;

2、“使用instanceof操作符检查“参数是否为正确的类型”,如果不是则返回false。所谓的正确的类型是指equals方法所在的那个类,或者是该类的父类或接口
3、把参数转化成正确的类型:因为上一步已经做过instanceof测试,所以确保转化会成功
4、对于该类的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配(其实就是比较两个对象的值是否相等了)
5、当你编写完equals方法之后,应该问自己三个问题:它是否是对称的传递的一致的?当然equals也必须满足其他两个约定(自反性、非空性)但是这两种约定通常会自动满足。

10.5 使用equals的注意点

  • 覆盖equals时总要覆盖hashCode(见第9条)
  • 不要企图让equals方法过于智能:不要想过度地去寻求各种等价关系,否则容易陷入各种麻烦
  • 不要将equals声明中的Object对象替换为其他类型,不然就不是重写equals了,而是重载了。加上@override可以避免这种错误发生
  • 使用Coogle开源的AutoValue框架,会自动生成这些方法。通过类的注释就能触发。这个框架比IDEA的好,因为IDEA的冗长,可读性差,无法自动追踪类的变化,因此需要测试。
    总结:不要轻易覆盖equals,如果覆盖,一定要比较这个类的所有关键域,并且查看它们是否遵守equals合约的五项条款

覆盖equals时总要覆盖hashCode

标签:instanceof,obj,Effective,读书笔记,Point,equals,Java,public,ColorPoint
来源: https://blog.csdn.net/yuxinganggame/article/details/111604004

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有