1. 程式人生 > >Java 覆蓋equals和hashCode方法

Java 覆蓋equals和hashCode方法

前言

覆蓋equals方法看起來似乎很簡單,但是有許多覆蓋方式會導致錯誤,並且後果非常嚴重,最容易避免這類問題的辦法就是不覆蓋equals方法。

什麼時候需要覆蓋equals方法?如果類具有自己特有的“邏輯相等”概念(不同於物件等同),而且超類還沒有覆蓋equals方法以實現期望的行為,這時需要覆蓋equals方法。

覆蓋equals

覆蓋equals方法時,必須遵守它的通用約定,如果你違反了它們,就會發現你的程式將表現不正常,甚至奔潰,而且很難找到失敗的根源。

通用約定

  1. 自反性。對於任何非null的引用值x、x,equals(x)必須返回true。
  2. 對稱性。對於任何非null的引用值x、y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true。
  3. 傳遞性。對於任何非null的引用值x、y、z,如果x.equals(y)返回true,並且y.equals(z)返回true,則x.equals(z)必須返回true。
  4. 一致性。對於任何非null的引用值x、y,只要equals的比較操作在物件中所用的資訊沒有被修改,多次呼叫x.equals(y)就會一致地返回true,或者一致地返回false。
  5. 非空性。對於任何非null的引用值x、x,equals(null),必須返回false。

一般IDE工具,如IntelliJ IDEA可以幫助實現equals方法覆蓋。基本上是符合以上約定的。

這裡寫圖片描述

實現高質量equals方法的訣竅

  1. 使用==操作符檢查“引數是否為這個物件的引用”。
  2. 使用instanceof操作符檢查“引數是否為正確的型別”。
  3. 把引數轉化為正確的型別。
  4. 對於該類的中每個關鍵域,檢查引數中的域是否與該物件中對應的域想匹配。

示例

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
(this==obj) return true; if (!(obj instanceof Point)) { return false; } Point p = (Point) obj; return p.x == x && p.y == y; } }

型別檢查為什麼不用getClass()

getClass測試代替instanceof 測試,只有當物件有相同的實現時,才能使物件等同。此外,使用instanceof 還可以省去對null的判斷。

    @Override
    public boolean equals(Object obj) {
        if (this==obj) return true;
        if (obj==null || this.getClass()!=obj.getClass()) return false;
        Point p = (Point) obj;
        return p.x == x && p.y == y;
    }

Point1,Point2繼承Point

    public static void point1ComparePoint2(){
        Point point1=new Point1(1,2);
        Point point2=new Point2(1,2);
        Point point3=new Point2(1,2);

        //不同實現的比較
        System.out.println(point1.equals(point2));
        System.out.println(point2.equals(point1));

        //相同實現的比較
        System.out.println(point2.equals(point3));

    }

執行結果:
false
false
true

覆蓋hashCode方法

重寫equals方法必須要重寫hashCode方法。如果不這樣做的話會違反Object.hashCode的通用約定,從而導致該類無法結合所有基於雜湊的集合一起正常運作。如HashMap、HashTable、HashSet。

    public static void pointListCompare(){
        Point point1=new Point(1,2);
        Point point2=new Point(1,2);
        Map<Point,String> mp=new HashMap<>();
        mp.put(point1,"Point1");
        System.out.println("point1==point2 "+(point1==point2));
        System.out.println("point1 hashCode is:"+(point1.hashCode()));
        System.out.println("point2 hashCode is:"+(point1.hashCode()));
        System.out.println(mp.containsKey(point1));
        System.out.println(mp.containsKey(point2));
    }

執行結果:
point1==point2 false
point1 hashCode is:2133927002
point2 hashCode is:1836019240
true
false

Point中新增hashCode()方法

    @Override
    public int hashCode() {
        //31是一個奇素數,有個很好的特性,乘法可以優化為移位和減法:31*i==(i<<5)-i。
        // 現在的VM可以自動完成這種優化。習慣上都使用素數來計算雜湊結果
        int result = x;
        result = 31 * result + y;
        return result;
    }

執行結果:
point1==point2 false
point1 hashCode is:33
point2 hashCode is:33
true
true

HaspMap有一項優化,可以將與每個項相關聯的雜湊碼快取起來,如果雜湊碼不匹配,也不必檢驗物件的等同性。

完美例項

不同型別的覆蓋方法和hashCode生成。

public class PerfectEquals {
    //基本型別
    private int x;
    private char c;
    private double d;
    private float f;
    private String name;//引用型別
    private Color color;//列舉型別
    private Point point;//引用型別
    private List<Point> pointList;//集合類


    public PerfectEquals(int x, char c, double d, float f, String name, Color color, Point point, List<Point> pointList) {
        this.x = x;
        this.c = c;
        this.d = d;
        this.f = f;
        this.name = name;
        this.color = color;
        this.point = point;
        this.pointList = pointList;
    }

    @Override
    public boolean equals(Object o) {
        //1. 使用==操作符檢查“引數是否為這個物件的引用”。
        if (this == o) return true;
        //2. 使用instanceof操作符檢查“引數是否為正確的型別”。
        if (!(o instanceof PerfectEquals)) return false;
        //3. 把引數轉化為正確的型別。
        PerfectEquals that = (PerfectEquals) o;

        //4. 對於該類的中每個關鍵域,檢查引數中的域是否與該物件中對應的域想匹配。
        if (x != that.x) return false;
        if (c != that.c) return false;
        if (Double.compare(that.d, d) != 0) return false;
        if (Float.compare(that.f, f) != 0) return false;
//      if (name != null ? !name.equals(that.name) : that.name != null) return false;//10000000 times compare cost time is 27-32ms
        if (name!=that.name|| name!=null&&!name.equals(that.name)) return false;//更快些,10000000 times compare cost time is 3-4ms
        if (color != that.color) return false;
//      if (point != null ? !point.equals(that.point) : that.point != null) return false;
        if (point!=that.point||point!=null&&!point.equals(that.point)) return false;
//      return pointList != null ? pointList.equals(that.pointList) : that.pointList == null;
        return pointList==that.pointList|| pointList!=null&&pointList.equals(that.pointList);
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = x;
        //31是一個奇素數,有個很好的特性,乘法可以優化為移位和減法:31*i==(i<<5)-i。
        // 現在的VM可以自動完成這種優化。習慣上都使用素數來計算雜湊結果
        result = 31 * result + (int) c;
        temp = Double.doubleToLongBits(d);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (f != +0.0f ? Float.floatToIntBits(f) : 0);
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (color != null ? color.hashCode() : 0);
        result = 31 * result + (point != null ? point.hashCode() : 0);
        result = 31 * result + (pointList != null ? pointList.hashCode() : 0);
        return result;
    }

    public static void main(String[] args) {
        List<Point> pointList=new ArrayList<>();
        Point point1=new Point(1,2);
        Point point2=new Point(2,2);
        Point point3=new Point(3,2);
        pointList.add(point1);
        pointList.add(point2);
        pointList.add(point3);

        PerfectEquals pe1=new PerfectEquals(12,'c',12D,21f,"Perfect",Color.RED,new Point(23,24),pointList);
        PerfectEquals pe2=new PerfectEquals(12,'c',12D,21f,"Perfect",Color.RED,new Point(23,24),pointList);

        long start=System.currentTimeMillis();
        for (int i=0;i<10000000;i++){
            pe1.equals(pe2);
        }
        long end=System.currentTimeMillis();
        System.out.println("compare cost time is :"+(end-start));
    }