zoukankan      html  css  js  c++  java
  • equals与hashCode

    当我们需要将自己的类存入HashMapHashSet时一般都要重写其equalshashCode方法,但在重写时要符合规范否则会出问题。

    1、equals方法

    首先equals方法需要满足如下几点性质:

    • 自反性:对于非空引用xx.equals(x)的的结果一定为真。
    • 对称性:对于非空引用xy,如果x.equals(y)为真,y.equals(x)一定为真。
    • 传递性:对应非空引用xyz,如果x.equals(y)为真,y.equals(z)为真,x.euqals(z)一定为真。
    • 幂等性:对于非空引用xy,如果两个对象没有被改变,多次调用x.equals(y),其返回值不变。

     这些性质中自反性与幂等性一般不会被破化,但对称性与传递性在一些情况下却无法满足。

    1.1、对称性

    public final class CaseInsensitiveString {
        private final String 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;
        }
    }

    上边的例子,CaseInsensitiveString在实现equals方法时对原始String类进行了支持。

    CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
    String s = "polish";
    

    考虑变量cisscis.equals(s)结果为真,但是s.equals(cis)为假,不满足对称性。

    List<CaseInsensitiveString> list = new ArrayList<>();
    list.add(cis);
    

    这时list.contains(s)的结果为假。 

    1.2、传递性  

    public class Point {
        private final int x;
        private final int y;
    
        @Override public boolean equals(Object o) {
            if (!(o instanceof Point))
                return false;
            Point p = (Point)o;
            return p.x == x && p.y == y;
        }
    }
    

    以一个二维坐点类为例,如果我们需要对其进行扩展,添加一个颜色属性。

    public class ColorPoint extends Point {
        private final Color color;
        public ColorPoint(int x, int y, Color color) {
            super(x, y);
            this.color = color;
        }
    }
    

    ColorPoint该如何实现equals方法呢,先看第一种实现方式:

     // 不满足对称性
    @Override public boolean equals(Object o) {
        if (!(o instanceof ColorPoint))
            return false;
        return super.equals(o) && ((ColorPoint) o).color == color;
    }
    

    这时PointColorPoint比较(point.equals(colorPoint))结果可以为真,但是ColorPointPoint比较时结果永远为假,即不满足对称性。第二种实现方式:

    // 不满足传递性
    @Override public boolean equals(Object o) {
        if (!(o instanceof Point))
            return false;
        if (!(o instanceof ColorPoint))
            return o.equals(this);
        return super.equals(o) && ((ColorPoint) o).color == color;
    }  

    这种方式在比较时区别对待Point跟ColorPoint,但假如有三个对象(1, 2 ,红),(1,2),(1,2,绿),(1,2,红)等于(1,2),(1,2)等于(1,2,绿),但是(1,2,红)却不等于(1,2,绿),即不满足传递性。

    1.3、模板

    实现equals方法一般可以遵循一个模板,首先用==操作符检查被比较对象是否是自己本身,然后用instanceof操作符检查类型,再将其转换为正确的类型,最后逐个对有意义的字段进行比较。

    public final class PhoneNumber {
        private final int areaCode, prefix, lineNum;
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNum == lineNum && pn.prefix == prefix
                    && pn.areaCode == areaCode;
        } 
    }

    2、hashCode方法

    重写equals方法后一定要记得重写hashCode方法,因为hashCode也要满足几条性质: 

    • 当一个对象的属性没有被修改时,多次调用其hashCode函数返回值不变。
    • 如果两个对象被equals函数判定为相等,那么这两个对象的hashCode函数的返回值也一定相等。
    • 如果两个对象被equals函数判定为不等,那么这两个对象的hashCode函数的返回值可以是相等的。

    这里说的对象的属性是指在equals函数中使用到的属性。上述性质中提到,如果两个对象被equals函数判定为相等,那这两个对象的hashCode函数的返回值必须是相等的,如果我们的类没有重写hashCode函数就无法满足这条性质,在使用HashMap函数时也会出问题。

    Map<PhoneNumber, String> m = new HashMap<>();
    m.put(new PhoneNumber(707, 867, 5309), "Jenny");  

    接着调用m.get(new PhoneNumber(707, 867,5309)),但它并不会返回"Jenny"而是null,因为PhoneNumber类没有重写hashCode函数,根据Object类的hashCode函数,新对象可能会被映射到另外的哈希桶中导致查找失败。

    重写hashCode函数也有章可循:

    1. 声明一个int类型的变量result,其值初始化为第一个属性的哈希值。
    2. 对于每个剩余的属性,分别计算其哈希值c
    3. 合并结果result = 31 * result + c

    分别计算对象属性哈希值时,根据属性的类别,方法如下:

    1. 原始类型:使用Type.hashCode(f)Type是原始类型的装箱类型。
    2. 引用类型:调用该对象的hashCode函数,如果对象为null则使用默认值,一般为0。
    3. 数组类型:使用Arrays.hashCode函数,如果数组为null则使用默认值,一般为0。

    对于上文中的PhoneNumber类,它的hashCode函数实现如下:

    @Override public int hashCode() {
        int result = Short.hashCode(areaCode);
        result = 31 * result + Short.hashCode(prefix);
        result = 31 * result + Short.hashCode(lineNum);
        return result;
    }
    

      

  • 相关阅读:
    C++中如何使用大整数__int 128
    全排列问题
    读书札记:瑞士法郎的因素
    读书札记:影响欧元的因素
    金融市场:最全的外汇平台资料大全(包括开户金额、点差、特色!)
    读书札记:澳大利亚元因素
    情感日记:祭衣文
    情感日记:第一次亲密的接触
    读书札记:美元影响的因素
    读书札记:外汇市场
  • 原文地址:https://www.cnblogs.com/mmmmar/p/8762349.html
Copyright © 2011-2022 走看看