【Java面試題】3 Java的"==","equals()"和"hashcode()"有什么區(qū)別

1. “==”比較的是什么

  • ==是一個(gè)運(yùn)算符
  • 對(duì)于8種基本的數(shù)據(jù)類型,比較的是變量所對(duì)應(yīng)內(nèi)存存儲(chǔ)的數(shù)值
  • 對(duì)于指向?qū)ο蟮淖兞?,比較的也是變量所對(duì)應(yīng)內(nèi)存存儲(chǔ)的數(shù)值(即指向的對(duì)象占用堆內(nèi)存的首地址),也就是比較變量是否指向同一個(gè)對(duì)象。
public class Test {
    public static void main(String[] args) {
        int a = 1, b = 1, c = 2;
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        System.out.println("a==b:" + (a == b));
        System.out.println("a==c:" + (a == c));

        System.out.println("s1==s2:" + (s1 == s2));
        System.out.println("s1==s3:" + (s1 == s3));
    }
}

輸出結(jié)果:

a==b:true
a==c:false
s1==s2:true
s1==s3:false

上述代碼中:
如果一個(gè)變量指向的數(shù)據(jù)是對(duì)象類型的,那么就涉及兩塊內(nèi)存,變量占用的棧內(nèi)存和對(duì)象占用的堆內(nèi)存。例如Object obj = new Object();變量obj是存儲(chǔ)在棧內(nèi)存,new Object()存儲(chǔ)在堆內(nèi)存,此時(shí),變量obj所對(duì)應(yīng)的內(nèi)存中存儲(chǔ)的數(shù)值就是對(duì)象占用的那塊內(nèi)存的首地址。

2. “equals”比較的是什么

  • equals是根類Object中的方法
    源代碼如下:
public boolean equals(Object obj) {
        return (this == obj);
    }
  • equals方法是用于比較兩個(gè)獨(dú)立對(duì)象的內(nèi)容是否相同,需要為類重寫equals方法。

例如,對(duì)于下面的代碼:

String a=new String("foo");
String b=new String("foo");

兩條new語(yǔ)句創(chuàng)建了兩個(gè)對(duì)象,然后用a與b這兩個(gè)變量分別指向了其中一個(gè)對(duì)象,這是兩個(gè)不同的對(duì)象,它們的首地址是不同的,即a和b中存儲(chǔ)的數(shù)值是不相同的,所以,表達(dá)式a==b將返回false,而String類重寫了equals方法,兩個(gè)變量中的內(nèi)容是相同的。所以,表達(dá)式a.equals(b)將返回true。

在實(shí)際開發(fā)中,我們經(jīng)常要比較傳遞進(jìn)行來的字符串內(nèi)容是否等,
例如,

String input = "xxxxx";
input.equals("quit");

許多人稍不注意就使用==進(jìn)行比較了,記住,字符串的比較基本上都是使用equals方法。

如果一個(gè)類沒有自己定義equals方法,那么它將繼承Object類的equals方法,Object類的equals方法的實(shí)現(xiàn)代碼如下:

boolean equals(Object o){
  return this==o;
}

總結(jié):
如果一個(gè)類沒有定義自己的的equals方法,它默認(rèn)的equals方法就是從Object繼承來的equals方法,就相當(dāng)于“==”操作符。也是在比較兩個(gè)變量指向的對(duì)象是否是同一對(duì)象,這時(shí)候使用equals和使用==會(huì)得到同樣的結(jié)果,如果你編寫的類希望能夠比較該類創(chuàng)建的兩個(gè)實(shí)例對(duì)象的內(nèi)容是否相同,那么你必須覆蓋equals方法,由你自己寫代碼來決定在什么情況即可認(rèn)為兩個(gè)對(duì)象的內(nèi)容是相同的。

String類中重寫的equals方法源碼:

public boolean equals(Object anObject) {
        if (this == anObject) {  //如果指向的是同一個(gè)地址,那內(nèi)容肯定相同
            return true;   
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
}

可以看出
String類中重寫的equals()是按照下面順序判斷的:

  1. 先判斷地址是否相同,若想等執(zhí)行2,否則返回false,
  2. 然后判斷是否是String類型,否則返回false,
  3. 然后依次判斷每一個(gè)字符是否相等,全部相等返回true,否則返回false

3. “hashcode()”

  • hashcode是Object的方法。
  • 默認(rèn)情況下,Object中的hashCode() 返回對(duì)象的32位jvm內(nèi)存地址。也就是說如果對(duì)象不重寫該方法,則返回相應(yīng)對(duì)象的32為JVM內(nèi)存地址。

String類源碼中重寫的hashCode方法如下:

public int hashCode() {
    int h = hash;    //Default to 0 ### String類中的私有變量,
    if (h == 0 && value.length > 0) {    //private final char value[]; ### Sting類中保存的字符串內(nèi)容的的數(shù)組
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

String源碼中使用private final char value[];保存字符串內(nèi)容,因此String是不可變的。

下面的例子,沒有重寫ComHashcode類的···hashCode···方法,所以直接返回32位對(duì)象在JVM中的地址;Long類重寫了hashCode方法,返回計(jì)算出的hashCode數(shù)值,String重寫了hashCode方法,返回了相同的數(shù)值。

public class ComHashcode {
    public static void main(String[] args) throws Exception {
        ComHashcode a = new ComHashcode();
        ComHashcode b = new ComHashcode();
        System.out.println(a.hashCode()); // 366712642
        System.out.println(b.hashCode()); // 1829164700
        String s1="helloworld";
        String s2="helloworld";
        String s3=new String("helloworld");
        System.out.println("s1:"+s1.hashCode());    //s1:-1524582912
        System.out.println("s2:"+s2.hashCode());    //s2:-1524582912
        System.out.println("s3:"+s3.hashCode());    //s3:-1524582912
        Long num1 = new Long(8);
        Long num2 = new Long(8);
        System.out.println(num1.hashCode()); // 8
        System.out.println(num2.hashCode()); // 8
    }
}

總結(jié):

  1. 綁定。當(dāng)equals方法被重寫時(shí),通常有必要重寫hashCode方法,以維護(hù) hashCode方法的常規(guī)協(xié)定,協(xié)定聲明相等對(duì)象必須具有相等的哈希碼。

  2. 綁定原因。Hashtable實(shí)現(xiàn)一個(gè)哈希表,為了成功地在哈希表中存儲(chǔ)和檢索對(duì)象,用作鍵的對(duì)象必須實(shí)現(xiàn) hashCode 方法和 equals 方法。
    equals()方法和hashCode()方法存在如下契約:
    如果兩個(gè)對(duì)象是相等的,那么他們的hashCode必須是相同的。
    如果兩個(gè)對(duì)象具有相同的hashCode,它們可以相等,也可以不相等。

  3. 默認(rèn)。
    ==默認(rèn)比較對(duì)象在JVM中的地址。
    hashCode 默認(rèn)返回對(duì)象在JVM中的存儲(chǔ)地址。
    equal比較對(duì)象,默認(rèn)也是比較對(duì)象在JVM中的地址,同==

重寫equals()方法而沒有重寫hashcode()方法造成的錯(cuò)誤:

import java.util.HashMap;

public class Cat {
    private String color;

    public Cat(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Cat)) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        return (this.color.equals(((Cat) obj).color));
    }

    public static void main(String[] args) {
        Cat cat1 = new Cat("白色");
        Cat cat2 = new Cat("黑色");
        System.out.println(cat1.equals(cat2));  //false,因?yàn)橹貙懥薳quals方法,且他們的顏色不一樣,所以返回false。
        HashMap<Cat, String> maps = new HashMap<>();
        maps.put(cat1, "白色");
        maps.put(cat2, "黑色");
        System.out.println(maps.get(new Cat("白色")));    //null,因?yàn)闆]有重寫hashcode方法,所以在查找的時(shí)候計(jì)算的hashcode值不一樣,無法找到,所以返回null。
    }
}

造成該錯(cuò)誤的原因就在于重寫equals方法后沒有重寫hashcode方法。

對(duì)于Java中所有類的超級(jí)父類java.lang.Object而言,其hashCode()的默認(rèn)實(shí)現(xiàn)是:對(duì)于不同的對(duì)象就返回不同的整型值。上述示例代碼中,Cat類沒有重寫Object的hashCode()方法。所以,這條代碼System.out.println(maps.get(new Cat("白色")));創(chuàng)建的Cat類和cat1的hasncode值不一樣,所以無法找到。

最后給出重寫hashcode()方法后的代碼:

import java.util.HashMap;

public class Cat {
    private String color;

    public Cat(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Cat)) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        return (this.color.equals(((Cat) obj).color));
    }

    @Override
    public int hashCode() {
        return this.color.hashCode();
    }

    public static void main(String[] args) {
        Cat cat1 = new Cat("白色");
        Cat cat2 = new Cat("黑色");
        System.out.println(cat1.equals(cat2)); // false
        HashMap<Cat, String> maps = new HashMap<>();
        maps.put(cat1, "白色");
        maps.put(cat2, "黑色");
        System.out.println(maps.get(new Cat("白色"))); // 白色
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,650評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,790評(píng)論 11 349
  • (一)Java部分 1、列舉出JAVA中6個(gè)比較常用的包【天威誠(chéng)信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,254評(píng)論 0 62
  • 轉(zhuǎn)自:http://blog.csdn.net/jackfrued/article/details/4492194...
    王帥199207閱讀 8,803評(píng)論 3 93
  • 一年前,我在博客寫過一篇文章,名字叫“生活應(yīng)該感性一點(diǎn)兒!”,里面寫了從十三歲到如今我們應(yīng)該會(huì)遇到的這樣的...
    賈香閱讀 431評(píng)論 0 2

友情鏈接更多精彩內(nèi)容