Effective Java2读书笔记-对于所有对象都通用的方法(一)

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

①约定的内容

  • 自反性。对于任何非null的引用值x。x.equals(x)必须返回true。
  • 对称性。对于任何非null的引用值x和y。当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
  • 传递性。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,则x.equals(z)也必须是true。
  • 一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true或者一致返回false。
  • 非空性。对于任何非null的引用值x,x.equals(null)必须返回false。

书中举了两个例子,主要是针对对称性和传递性。下面先看一下对称性的反例

public final class CaseInsensitiveString {
    private final String s;

    public CaseInsensitiveString(String s) {
        if (s == null)
            throw new NullPointerException();
        this.s = s;
    }
    @Override
    public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
        if (o instanceof String)
            return s.equalsIgnoreCase((String) o);
        return false;
    }

    public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
        String s = "polish";
        System.out.println(cis.equals(s) + "  " + s.equals(cis));
    }
}

该例子人为封装了一个不区分大小写的字符串类。其中,equals方法传入普通字符串时,也是可以比较的。但是反过来,普通字符串调用equals方法传入CaseInsensitiveString类型的字符串,就返回false了(相信实际中没人会这么用,这里只是给大家提个醒)。

下面再看一个传递性的反例

点类,横纵坐标都相等即认为是相等

//点类
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 o) {
        if (!(o instanceof Point))
            return false;
        Point p = (Point) o;
        return p.x == x && p.y == y;
    }

    @Override
    public int hashCode() {
        return 31 * x + y;
    }
}

带颜色的点类,横纵坐标都相等,且颜色也相同就认为是相等

public class ColorPoint extends Point {
    private final Color color;

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

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof ColorPoint))
            return false;
        return super.equals(o) && ((ColorPoint) o).color == color;
    }

    public static void main(String[] args) {
        ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
        Point p2 = new Point(1, 2);
        ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
        System.out.printf("%s %s %s%n", p2.equals(p1), p2.equals(p3),
                p1.equals(p3));
    }
}

public enum Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

这时,p2.equals(p1)是true,p2.equals(p3)也是true,但是p1.equals(p3)是false,不符合传递性。归根结底的原因是p2是基类生成的对象,p1和p3是子类生成的对象。因此,p2和p1、p3比较时,就变色盲了。

②实现高质量equals方法的诀窍。

  • 首先使用“==”操作符检查传入的是否是这个对象的引用。
  • 然后使用Instanceof操作符检查“参数类型是否正确”。
  • 把Object强转为正确的类型再进行一系列比较。
  • 当编写完后,注意对称性和传递性。
  • 注意,一定要覆盖Object类中equals方法,不要重写成equals(MyClass o)。

第9条:覆盖equals时总要覆盖hashCode

首先看一个例子,它完全是根据第8条的诀窍写出来的。

public final class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    private static void rangeCheck(int arg, int max, String name) {
        if (arg < 0 || arg > max)
            throw new IllegalArgumentException(name + ": " + arg);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
    }
}

但是,返回结果却不是Jenny,而是null。这是由于PhoneNumber类没有覆盖hashCode方法,从而导致两个相等的实例具有不相等的散列码,违反了hashCode的约定,因此put方法把电话号码对象存放在一个散列桶中,get方法却在另一个散列桶中查找电话号码。即使凑巧放到一个桶中,get仍然返回null,因为HashMap有一项优化,如果散列码不匹配,直接不检验对象的等同性了。

修正问题的办法就是提供一个hashCode方法。例如

@Override
public int hashCode(){return 42;}

它确保了相同的对象总是有同样的散列码。但它也是极为恶劣的,因为它使得每一个对象都有同样的散列码。这样,所有的对象都会被放到同一个散列桶中,使得散列退化成了链表。理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。

对于不同的域(上述例子是3个short),有一套不同的计划散列码的规则。好在Eclipse已经可以帮我们自动生成了。

至于为什么要使用31这个数字,博文http://blog.csdn.net/mingli198611/article/details/10062791给出了分析

A.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)

B.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)

C.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

D.并且31只占用5bits,相乘造成数据溢出的概率较小。

时间: 2024-12-14 08:20:35

Effective Java2读书笔记-对于所有对象都通用的方法(一)的相关文章

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

第10条:始终要覆盖toString 这一条没什么好讲的,就是说默认的toString方法打印出来的是类名[email protected]+十六进制哈希码的值.我们应该覆盖它,使它能够展示出一些更为详细清晰的信息,这个看实际情况吧. 第11条:谨慎地覆盖clone 有时候会出现这样的场景,你需要备份一些数据,对其一进行修改时,另外一个不受影响.这样,直接Foo a = new Foo(); Foo b = a; 是不可行的,b引用和a指向的是同一个对象. 这一条讲了很多,最后的总结是,Clon

Effective Java2读书笔记-对于所有对象都通用的方法(三)

第12条:考虑实现Comparable接口 这一条非常简单.就是说,如果类实现了Comparable接口,覆盖comparaTo方法. 就可以使用Arrays.sort(a)对数组a进行排序. 它与equals方法有点类似,但是,因为Comparable接口是参数化的,而且comparable方法时静态的类型,因此不必进行类型检查,也不需要对它的参数进行类型转换.返回负值代表小,正值代表大,0代表相等.

effective java读书笔记——对于所有对象都通用的方法

Java中的所有类都继承自Object类,Object类中有许多通用的方法,这一章要讨论的是:对于Object类中的通用方法,我们的类要不要继承,以及继承时需要注意的事项. 第1条:equals(),覆盖时请遵守通用约定 首先看一下不需要覆盖的情况: 1.类的每个实例本质上是唯一的.(比如Static的,单例的等等),这样不需要特意覆盖equals方法,用Object类的equals()方法就足够了 2.不关心类是否实现了“逻辑相等”的测试功能.我们用equals的目的就是判断两个对象是否是“逻

Effective Java 学习笔记之所有对象都通用的方法

一.覆盖equals时请遵守通用约定 1.满足下列任何一个条件时,不需要覆盖equals方法 a.类的每个实例本质上都是唯一的.此时就是Object中equals方法所表达的含义. b.不关心类是否提供了“逻辑相等”的测试功能 c.超类中覆盖的equals方法,使用于子类,不需要自己再覆盖了. d.类是私有或者包级私有,并确定equals方法永远不会被调用,可以不覆盖.或者覆盖equals方法,内容为抛出异常. 2.高质量equals方法的诀窍: a.使用==操作符检测“参数是否为这个对象的引用

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

3.1 覆盖equals时请遵守通用约定 什么时候应该覆盖Object.equals()方法呢? 如果类具有自己特有的"逻辑相等"概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. Object.equals()方法具有自反性.对称性.传递性.一致性和与null比较返回false的特点. 实现高质量equals方法的诀窍: (1)使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true,这

Effective java 第三章对于所有对象都通用的方法(一) 读书笔记

对于所有对象都通用的方法 覆盖equals时请遵守通用约定 类的每个实例本质上都是唯一的. 不关心类是否提供了逻辑相等的测试功能 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的. 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用. throw new AssertionError() 一般覆盖Object.equals都是值类 但有一种值类不需要覆盖equals方法,即实例受控,确保每个值至多只存在一个对象的类.如枚举 覆盖equals方法,通用约定. 自

[Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第八 ---- ?条

这一章主要讲解Object类中的方法, Object类是所有类的父类,所以它的方法也称得上是所有对象都通用的方法 第八条 覆盖equals时需要遵守的约定 Object中的equals实现,就是直接对对象进行相等的比较: public boolean equals(Object obj) { return (this == obj); } 那么什么时候需要覆盖equals呢? 当你的类有自己的逻辑相等,而不是对象相等时,应该自己实现equals,比如Date和Interger,他们的相等比较不仅

effective java-读书笔记-第三章 对于所有对象都通用的方法

个人博客同步发布:effective java-读书笔记-第三章 对于所有对象都通用的方法 第三章 对于所有对象都通用的方法 所有非final方法(equals.hashCode.toString.clone.finalize)都有明确的通用约定,因为它们被设计成是要被覆盖的,如果不遵守,基于散列的集合(HashMap.HashSet.HashTable)可能无法结合该类一起运作. 第8条 覆盖equals时请遵守通用约定 覆盖equals规范: 自反性(reflexive).对于任何非null

Effective Java:对于所有对象都通用的方法

前言: 读这本书第1条规则的时候就感觉到这是一本很好的书,可以把我们的Java功底提升一个档次,我还是比较推荐的.这里我主要就关于覆盖equals.hashCode和toString方法来做一个笔记总结,希望能够与君共勉. 概述: 这一章主要是说明一些对于所有对象都通用的方法.我们知道Java的多态是其特色之一,而多态的体现方式中就有一种方式叫做"重写".这些概念性的东西我想在大学我们学习Java的初期,老师就会如数家珍一样地灌输给我们,不过,在那个时候有多少人真的了解了什么是重载,什