天天看點

阿瑟Java (14):利用 hashCode()、equals() 如何判斷對象相等?

作者:阿瑟傑克斯

定義差別

  • equals() :
    • 作用是判斷兩個對象是否相等。
  • hashCode():
    • 作用是擷取哈希碼,也稱為散列碼;
    • 它實際上是傳回一個 int 整數。
    • 這個哈希碼的作用是确定對象在哈希表中的索引位置。

一個類本身不建立散清單

  • 該類的 hashCode() 和 equals() 沒有關系,equals() 用來比較該類的兩個對象是否相等,而 hashCode() 則根本沒有任何作用。
  • equals() 相等的情況下,hashCode() 也不一定相等。
/**
 * @desc 比較equals()傳回true和傳回false時,hashCode()的值。
 */
public class NormalHashCodeTest{

    public static void main(String[] args) {
        // 建立2個相同内容的Person對象,
        // 再用equals比較它們是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
    }

    /* Person類 */
    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String toString() {
            return name + " - " +age;
        }

        /* 覆寫equals方法 */
        public boolean equals(Object obj){  
            if(obj == null){  
                return false;  
            }  

            //如果是同一個對象傳回true,反之傳回false  
            if(this == obj){  
                return true;  
            }  

            //判斷是否類型相同  
            if(this.getClass() != obj.getClass()){  
                return false;  
            }  
		   /* 比較name和age的值 */
            Person person = (Person)obj;  
            return name.equals(person.name) && age==person.age;  
        } 
    }
}           
  • 運作結果:
p1.equals(p2) : true; p1(1169863946) p2(1901116749)
p1.equals(p3) : false; p1(1169863946) p3(2131949076)           
  • 從結果也可以看出:p1 和 p2 相等的情況下,hashCode() 也不一定相等。

一個類本身會建立散清單

  • 該類的 hashCode() 和 equals() 是有關系的。比如 HashSet, Hashtable, HashMap 等等
    • 如果兩個對象相等,那麼它們的 hashCode() 一定相等。這裡的相等是指,通過equals()比較兩個對象時傳回true。
    • 如果兩個對象 hashCode() 相等,但它們并不一定相等。因為在散清單中,hashCode()相等,即兩個鍵值對的哈希值相等。然而哈希值相等,并不一定能得出鍵值對相等。補充說一句:“兩個不同的鍵值對,哈希值相等”,這就是哈希沖突。
  • 在這種情況下。若要判斷兩個對象是否相等,除了要覆寫 equals() 之外,也要覆寫hashCode() 函數。否則,equals() 無效。
  • 舉例
    • 建立 Person 類的 HashSet 集合,必須同時覆寫 Person 類的 equals() 和 hashCode() 方法。
  • 隻是覆寫 equals() 方法
    • 我們會發現,equals() 方法沒有達到我們想要的效果。
import java.util.*;
import java.lang.Comparable;

/**
 * @desc 比較equals() 傳回true 以及 傳回false時, hashCode()的值。
 *
 */
public class ConflictHashCodeTest1{

    public static void main(String[] args) {
        // 建立Person對象,
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);

        // 建立HashSet對象 
        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);
        set.add(p3);

        // 比較p1 和 p2, 并列印它們的hashCode()
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        // 列印set
        System.out.printf("set:%s\n", set);
    }

    /**
     * @desc Person類。
     */
    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String toString() {
            return "("+name + ", " +age+")";
        }

        /** 
         * @desc 覆寫equals方法 
         */  
        @Override
        public boolean equals(Object obj){  
            if(obj == null){  
                return false;  
            }  

            //如果是同一個對象傳回true,反之傳回false  
            if(this == obj){  
                return true;  
            }  

            //判斷是否類型相同  
            if(this.getClass() != obj.getClass()){  
                return false;  
            }  

            Person person = (Person)obj;  
            return name.equals(person.name) && age==person.age;  
        } 
    }
}           
    • 運作結果:
p1.equals(p2) : true; p1(1169863946) p2(1690552137)
set:[(eee, 100), (eee, 100), (aaa, 200)]           
    • 結果分析:
      • 我們重寫了Person的equals()。但是,很奇怪的發現:HashSet中仍然有重複元素:p1 和 p2。為什麼會出現這種情況呢?
      • 這是因為雖然 p1 和 p2 的内容相等,但是它們的 hashCode() 不等;是以,HashSet 在添加 p1 和 p2 的時候,認為它們不相等。
  • 同時覆寫 equals() 和 hashCode() 方法呢?
  • import java.util.*;
    import java.lang.Comparable;
    
    /**
     * @desc 比較equals() 傳回true 以及 傳回false時, hashCode()的值。
     *
     */
    public class ConflictHashCodeTest2{
    
        public static void main(String[] args) {
            // 建立Person對象,
            Person p1 = new Person("eee", 100);
            Person p2 = new Person("eee", 100);
            Person p3 = new Person("aaa", 200);
            Person p4 = new Person("EEE", 100);
    
            // 建立HashSet對象 
            HashSet set = new HashSet();
            set.add(p1);
            set.add(p2);
            set.add(p3);
    
            // 比較p1 和 p2, 并列印它們的hashCode()
            System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
            // 比較p1 和 p4, 并列印它們的hashCode()
            System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
            // 列印set
            System.out.printf("set:%s\n", set);
        }
    
        /**
         * @desc Person類。
         */
        private static class Person {
            int age;
            String name;
    
            public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
    
            public String toString() {
                return name + " - " +age;
            }
    
            /** 
             * @desc重寫hashCode 
             */  
            @Override
            public int hashCode(){  
                int nameHash =  name.toUpperCase().hashCode();
                return nameHash ^ age;
            }
    
            /** 
             * @desc 覆寫equals方法 
             */  
            @Override
            public boolean equals(Object obj){  
                if(obj == null){  
                    return false;  
                }  
    
                //如果是同一個對象傳回true,反之傳回false  
                if(this == obj){  
                    return true;  
                }  
    
                //判斷是否類型相同  
                if(this.getClass() != obj.getClass()){  
                    return false;  
                }  
    
                Person person = (Person)obj;  
                return name.equals(person.name) && age==person.age;  
            } 
        }
    }           
      • 運作結果:
    p1.equals(p2) : true; p1(68545) p2(68545)
    p1.equals(p4) : false; p1(68545) p4(68545)
    set:[aaa - 200, eee - 100]           
      • 結果分析:
        • 這下,equals() 生效了,HashSet 中沒有重複元素。
        • 比較 p1 和 p2,我們發現:它們的 hashCode() 相等,通過 equals() 比較它們也傳回 true。是以,p1 和 p2 被視為相等。
        • 比較 p1 和 p4,我們發現:雖然它們的 hashCode() 相等;但是,通過 equals() 比較它們傳回 false。是以,p1 和 p4 被視為不相等。