JDK1.8源碼(一)——java.lang.Object類(lèi)

本系列博客將對(duì)JDK1.8版本的相關(guān)類(lèi)從源碼層次進(jìn)行介紹愈诚,JDK8的下載地址代虾。

首先介紹JDK中所有類(lèi)的基類(lèi)——java.lang.Object。

Object 類(lèi)屬于 java.lang 包丽声,此包下的所有類(lèi)在使用時(shí)無(wú)需手動(dòng)導(dǎo)入乎澄,系統(tǒng)會(huì)在程序編譯期間自動(dòng)導(dǎo)入。Object 類(lèi)是所有類(lèi)的基類(lèi)螃宙,當(dāng)一個(gè)類(lèi)沒(méi)有直接繼承某個(gè)類(lèi)時(shí)蛮瞄,默認(rèn)繼承Object類(lèi),也就是說(shuō)任何類(lèi)都直接或間接繼承此類(lèi)谆扎,Object 類(lèi)中能訪問(wèn)的方法在所有類(lèi)中都可以調(diào)用挂捅,下面我們會(huì)分別介紹Object 類(lèi)中的所有方法。

1堂湖、Object 類(lèi)的結(jié)構(gòu)圖

image

Object.class類(lèi)

/*
 * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */

package java.lang;

/**
 * Class {@code Object} is the root of the class hierarchy.
 * Every class has {@code Object} as a superclass. All objects,
 * including arrays, implement the methods of this class.
 *
 * @author  unascribed
 * @see     java.lang.Class
 * @since   JDK1.0
 */
public class Object {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    public final native Class<?> getClass();

    public native int hashCode();

    public boolean equals(Object obj) {
        return (this == obj);
    }

    protected native Object clone() throws CloneNotSupportedException;
    
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    public final native void notify();
    
    public final native void notifyAll();
    
    public final native void wait(long timeout) throws InterruptedException;
    
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                    "nanosecond timeout value out of range");
        }
        if (nanos > 0) {
            timeout++;
        }
        wait(timeout);
    }
    
    public final void wait() throws InterruptedException {
        wait(0);
    }
    
    protected void finalize() throws Throwable { }
}

2闲先、 為什么java.lang包下的類(lèi)不需要手動(dòng)導(dǎo)入?

不知道大家注意到?jīng)]无蜂,我們?cè)谑褂弥T如Date類(lèi)時(shí)伺糠,需要手動(dòng)導(dǎo)入import java.util.Date,再比如使用File類(lèi)時(shí)酱讶,也需要手動(dòng)導(dǎo)入import java.io.File退盯。但是我們?cè)谑褂肙bject類(lèi),String 類(lèi),Integer類(lèi)等不需要手動(dòng)導(dǎo)入渊迁,而能直接使用慰照,這是為什么呢?

這里先告訴大家一個(gè)結(jié)論:使用 java.lang 包下的所有類(lèi)琉朽,都不需要手動(dòng)導(dǎo)入毒租。

另外我們介紹一下Java中的兩種導(dǎo)包形式,導(dǎo)包有兩種方法:

①箱叁、單類(lèi)型導(dǎo)入(single-type-import)墅垮,例如import java.util.Date

②、按需類(lèi)型導(dǎo)入(type-import-on-demand)耕漱,例如import java.util.*

單類(lèi)型導(dǎo)入比較好理解算色,我們編程所使用的各種工具默認(rèn)都是按照單類(lèi)型導(dǎo)包的,需要什么類(lèi)便導(dǎo)入什么類(lèi)螟够,這種方式是導(dǎo)入指定的public類(lèi)或者接口灾梦;

按需類(lèi)型導(dǎo)入齿梁,比如 import java.util.*吊洼,可能看到后面的 *拧篮,大家會(huì)以為是導(dǎo)入java.util包下的所有類(lèi)津畸,其實(shí)并不是這樣邻储,我們根據(jù)名字按需導(dǎo)入要知道他是按照需求導(dǎo)入缎罢,并不是導(dǎo)入整個(gè)包下的所有類(lèi)一屋。

Java編譯器會(huì)從啟動(dòng)目錄(bootstrap)擎值,擴(kuò)展目錄(extension)和用戶(hù)類(lèi)路徑下去定位需要導(dǎo)入的類(lèi)辈赋,而這些目錄進(jìn)僅僅是給出了類(lèi)的頂層目錄鲫忍,編譯器的類(lèi)文件定位方法大致可以理解為如下公式:

頂層路徑名 \ 包名 \ 文件名.class = 絕對(duì)路徑

單類(lèi)型導(dǎo)入我們知道包名和文件名,所以編譯器可以一次性查找定位到所要的類(lèi)文件钥屈。按需類(lèi)型導(dǎo)入則比較復(fù)雜饲窿,編譯器會(huì)把包名和文件名進(jìn)行排列組合,然后對(duì)所有的可能性進(jìn)行類(lèi)文件查找定位焕蹄。例如:

package com;

import java.io.*;

import java.util.*;

如果我們文件中使用到了 File 類(lèi)逾雄,那么編譯器會(huì)根據(jù)如下幾個(gè)步驟來(lái)進(jìn)行查找 File 類(lèi):

①、File       // File類(lèi)屬于無(wú)名包腻脏,就是說(shuō)File類(lèi)沒(méi)有package語(yǔ)句鸦泳,編譯器會(huì)首先搜索無(wú)名包

②、com.File     // File類(lèi)屬于當(dāng)前包永品,就是我們當(dāng)前編譯類(lèi)的包路徑

③做鹰、java.lang.File   //由于編譯器會(huì)自動(dòng)導(dǎo)入java.lang包,所以也會(huì)從該包下查找

④鼎姐、java.io.File

⑤钾麸、java.util.File

......

需要注意的地方就是更振,編譯器找到j(luò)ava.io.File類(lèi)之后并不會(huì)停止下一步的尋找,而要把所有的可能性都查找完以確定是否有類(lèi)導(dǎo)入沖突饭尝。假設(shè)此時(shí)的頂層路徑有三個(gè)肯腕,那么編譯器就會(huì)進(jìn)行3*5=15次查找。

如果在查找完成后钥平,編譯器發(fā)現(xiàn)了兩個(gè)同名的類(lèi)实撒,那么就會(huì)報(bào)錯(cuò)。要?jiǎng)h除你不用的那個(gè)類(lèi)涉瘾,然后再編譯知态。

所以我們可以得出這樣的結(jié)論:按需類(lèi)型導(dǎo)入是絕對(duì)不會(huì)降低Java代碼的執(zhí)行效率的,但會(huì)影響到Java代碼的編譯速度立叛。所以我們?cè)诰幋a時(shí)最好是使用單類(lèi)型導(dǎo)入负敏,這樣不僅能提高編譯速度,也能避免命名沖突秘蛇。

講清楚Java的兩種導(dǎo)包類(lèi)型了原在,我們?cè)诨氐綖槭裁纯梢灾苯邮褂?Object 類(lèi),看到上面查找類(lèi)文件的第③步彤叉,編譯器會(huì)自動(dòng)導(dǎo)入 java.lang 包,那么當(dāng)然我們能直接使用了村怪。至于原因秽浇,因?yàn)橛玫亩啵崆凹虞d了甚负,省資源柬焕。

3、類(lèi)構(gòu)造器

我們知道類(lèi)構(gòu)造器是創(chuàng)建Java對(duì)象的途徑之一梭域,通過(guò)new 關(guān)鍵字調(diào)用構(gòu)造器完成對(duì)象的實(shí)例化斑举,還能通過(guò)構(gòu)造器對(duì)對(duì)象進(jìn)行相應(yīng)的初始化。一個(gè)類(lèi)必須要有一個(gè)構(gòu)造器的存在病涨,如果沒(méi)有顯示聲明富玷,那么系統(tǒng)會(huì)默認(rèn)創(chuàng)造一個(gè)無(wú)參構(gòu)造器,在JDK的Object類(lèi)源碼中既穆,是看不到構(gòu)造器的赎懦,系統(tǒng)會(huì)自動(dòng)添加一個(gè)無(wú)參構(gòu)造器。我們可以通過(guò):

Object obj = new Object()幻工;構(gòu)造一個(gè)Object類(lèi)的對(duì)象励两。

4、equals 方法

通常很多面試題都會(huì)問(wèn) equals() 方法和 == 運(yùn)算符的區(qū)別囊颅,== 運(yùn)算符用于比較基本類(lèi)型的值是否相同当悔,或者比較兩個(gè)對(duì)象的引用是否相等傅瞻,而 equals 用于比較兩個(gè)對(duì)象是否相等,這樣說(shuō)可能比較寬泛盲憎,兩個(gè)對(duì)象如何才是相等的呢嗅骄?這個(gè)標(biāo)尺該如何定?
我們可以看看 Object 類(lèi)中的equals 方法:

public boolean equals(Object obj) {
    return (this == obj);
}

可以看到焙畔,在 Object 類(lèi)中掸读,== 運(yùn)算符和 equals 方法是等價(jià)的,都是比較兩個(gè)對(duì)象的引用是否相等宏多,從另一方面來(lái)講儿惫,如果兩個(gè)對(duì)象的引用相等,那么這兩個(gè)對(duì)象一定是相等的伸但。對(duì)于我們自定義的一個(gè)對(duì)象肾请,如果不重寫(xiě) equals 方法,那么在比較對(duì)象的時(shí)候就是調(diào)用 Object 類(lèi)的 equals 方法更胖,也就是用 == 運(yùn)算符比較兩個(gè)對(duì)象铛铁。我們可以看看 String 類(lèi)中的重寫(xiě)的 equals 方法:

public boolean equals(Object anObject) {
        if (this == anObject) {
            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 是引用類(lèi)型,比較時(shí)不能比較引用是否相等却妨,重點(diǎn)是字符串的內(nèi)容是否相等饵逐。所以 String 類(lèi)定義兩個(gè)對(duì)象相等的標(biāo)準(zhǔn)是字符串內(nèi)容都相同。

在Java規(guī)范中彪标,對(duì) equals 方法的使用必須遵循以下幾個(gè)原則:

①倍权、自反性:對(duì)于任何非空引用值 x,x.equals(x) 都應(yīng)返回 true捞烟。

②薄声、對(duì)稱(chēng)性:對(duì)于任何非空引用值 x 和 y,當(dāng)且僅當(dāng) y.equals(x) 返回 true 時(shí)题画,x.equals(y) 才應(yīng)返回 true默辨。

③、傳遞性:對(duì)于任何非空引用值 x苍息、y 和 z缩幸,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true竞思,那么 x.equals(z) 應(yīng)返回 true桌粉。

④、一致性:對(duì)于任何非空引用值 x 和 y衙四,多次調(diào)用 x.equals(y) 始終返回 true 或始終返回 false铃肯,前提是對(duì)象上 equals 比較中所用的信息沒(méi)有被修改

⑤、對(duì)于任何非空引用值 x传蹈,x.equals(null) 都應(yīng)返回 false押逼。

下面我們自定義一個(gè) Person 類(lèi)步藕,然后重寫(xiě)其equals 方法,比較兩個(gè) Person 對(duì)象:

package com.ys.bean;
/**
 * Create by vae
 */
public class Person {
    private String pname;
    private int page;

    public Person(){}

    public Person(String pname,int page){
        this.pname = pname;
        this.page = page;
    }
    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj){//引用相等那么兩個(gè)對(duì)象當(dāng)然相等
            return true;
        }
        if(obj == null || !(obj instanceof  Person)){//對(duì)象為空或者不是Person類(lèi)的實(shí)例
            return false;
        }
        Person otherPerson = (Person)obj;
        if(otherPerson.getPname().equals(this.getPname()) && otherPerson.getPage()==this.getPage()){
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Person p1 = new Person("Tom",21);
        Person p2 = new Person("Marry",20);
        System.out.println(p1==p2);//false
        System.out.println(p1.equals(p2));//false

        Person p3 = new Person("Tom",21);
        System.out.println(p1.equals(p3));//true
    }

}

通過(guò)重寫(xiě) equals 方法挑格,我們自定義兩個(gè)對(duì)象相等的標(biāo)尺為Person對(duì)象的兩個(gè)屬性都相等咙冗,則對(duì)象相等,否則不相等漂彤。如果不重寫(xiě) equals 方法雾消,那么始終是調(diào)用 Object 類(lèi)的equals 方法,也就是用 == 比較兩個(gè)對(duì)象在棧內(nèi)存中的引用地址是否相等挫望。

這時(shí)候有個(gè)Person 類(lèi)的子類(lèi) Man立润,也重寫(xiě)了 equals 方法:

package com.ys.bean;
/**
 * Create by vae
 */
public class Man extends Person{
    private String sex;

    public Man(String pname,int page,String sex){
        super(pname,page);
        this.sex = sex;
    }
    @Override
    public boolean equals(Object obj) {
        if(!super.equals(obj)){
            return false;
        }
        if(obj == null || !(obj instanceof  Man)){//對(duì)象為空或者不是Person類(lèi)的實(shí)例
            return false;
        }
        Man man = (Man) obj;
        return sex.equals(man.sex);
    }

    public static void main(String[] args) {
        Person p = new Person("Tom",22);
        Man m = new Man("Tom",22,"男");

        System.out.println(p.equals(m));//true
        System.out.println(m.equals(p));//false
    }
}

通過(guò)打印結(jié)果我們發(fā)現(xiàn) person.equals(man)得到的結(jié)果是 true,而man.equals(person)得到的結(jié)果卻是false,這顯然是不正確的媳板。

問(wèn)題出現(xiàn)在 instanceof 關(guān)鍵字上桑腮,關(guān)于 instanceof 關(guān)鍵字的用法,可以參考我的這篇文章:http://www.cnblogs.com/ysocean/p/8486500.html

Man 是 Person 的子類(lèi)蛉幸,person instanceof Man 結(jié)果當(dāng)然是false破讨。這違反了我們上面說(shuō)的對(duì)稱(chēng)性。

實(shí)際上用 instanceof 關(guān)鍵字是做不到對(duì)稱(chēng)性的要求的奕纫。這里推薦做法是用 getClass()方法取代 instanceof 運(yùn)算符提陶。getClass() 關(guān)鍵字也是 Object 類(lèi)中的一個(gè)方法,作用是返回一個(gè)對(duì)象的運(yùn)行時(shí)類(lèi)匹层,下面我們會(huì)詳細(xì)講解隙笆。

那么 Person 類(lèi)中的 equals 方法為

public boolean equals(Object obj) {
        if(this == obj){//引用相等那么兩個(gè)對(duì)象當(dāng)然相等
            return true;
        }
        if(obj == null || (getClass() != obj.getClass())){//對(duì)象為空或者不是Person類(lèi)的實(shí)例
            return false;
        }
        Person otherPerson = (Person)obj;
        if(otherPerson.getPname().equals(this.getPname()) && otherPerson.getPage()==this.getPage()){
            return true;
        }
        return false;
    }

打印結(jié)果 person.equals(man)得到的結(jié)果是 false,man.equals(person)得到的結(jié)果也是false又固,滿(mǎn)足對(duì)稱(chēng)性。

注意:使用 getClass 不是絕對(duì)的煤率,要根據(jù)情況而定仰冠,畢竟定義對(duì)象是否相等的標(biāo)準(zhǔn)是由程序員自己定義的。而且使用 getClass 不符合多態(tài)的定義蝶糯,比如 AbstractSet 抽象類(lèi)洋只,它有兩個(gè)子類(lèi) TreeSet 和 HashSet,他們分別使用不同的算法實(shí)現(xiàn)查找集合的操作,但無(wú)論集合采用哪種方式實(shí)現(xiàn)昼捍,都需要擁有對(duì)兩個(gè)集合進(jìn)行比較的功能识虚,如果使用 getClass 實(shí)現(xiàn)equals方法的重寫(xiě),那么就不能在兩個(gè)不同子類(lèi)的對(duì)象進(jìn)行相等的比較妒茬。而且集合類(lèi)比較特殊担锤,其子類(lèi)是不需要自定義相等的概念的。

所以什么時(shí)候使用 instanceof 運(yùn)算符乍钻,什么時(shí)候使用 getClass() 有如下建議:

①肛循、如果子類(lèi)能夠擁有自己的相等概念铭腕,則對(duì)稱(chēng)性需求將強(qiáng)制采用 getClass 進(jìn)行檢測(cè)。

②多糠、如果有超類(lèi)決定相等的概念累舷,那么就可以使用 instanceof 進(jìn)行檢測(cè),這樣可以在不同的子類(lèi)的對(duì)象之間進(jìn)行相等的比較夹孔。

下面給出一個(gè)完美的 equals 方法的建議:

1被盈、顯示參數(shù)命名為 otherObject,稍后會(huì)將它轉(zhuǎn)換成另一個(gè)叫做 other 的變量搭伤。

2只怎、判斷比較的兩個(gè)對(duì)象引用是否相等,如果引用相等那么表示是同一個(gè)對(duì)象闷畸,那么當(dāng)然相等

3尝盼、如果 otherObject 為 null,直接返回false佑菩,表示不相等

4盾沫、比較 this 和 otherObject 是否是同一個(gè)類(lèi):如果 equals 的語(yǔ)義在每個(gè)子類(lèi)中有所改變,就使用 getClass 檢測(cè)殿漠;如果所有的子類(lèi)都有統(tǒng)一的定義赴精,那么使用 instanceof 檢測(cè)

5、將 otherObject 轉(zhuǎn)換成對(duì)應(yīng)的類(lèi)類(lèi)型變量

6绞幌、最后對(duì)對(duì)象的屬性進(jìn)行比較蕾哟。使用 == 比較基本類(lèi)型,使用 equals 比較對(duì)象莲蜘。如果都相等則返回true谭确,否則返回false。注意如果是在子類(lèi)中定義equals票渠,則要包含 super.equals(other)

下面我們給出 Person 類(lèi)中完整的 equals 方法的書(shū)寫(xiě):

@Override
    public boolean equals(Object otherObject) {
        //1逐哈、判斷比較的兩個(gè)對(duì)象引用是否相等,如果引用相等那么表示是同一個(gè)對(duì)象问顷,那么當(dāng)然相等
        if(this == otherObject){
            return true;
        }
        //2昂秃、如果 otherObject 為 null,直接返回false杜窄,表示不相等
        if(otherObject == null ){//對(duì)象為空或者不是Person類(lèi)的實(shí)例
            return false;
        }
        //3肠骆、比較 this 和 otherObject 是否是同一個(gè)類(lèi)(注意下面兩個(gè)只能使用一種)
        //3.1:如果 equals 的語(yǔ)義在每個(gè)子類(lèi)中所有改變,就使用 getClass 檢測(cè)
        if(this.getClass() != otherObject.getClass()){
            return false;
        }
        //3.2:如果所有的子類(lèi)都有統(tǒng)一的定義塞耕,那么使用 instanceof 檢測(cè)
        if(!(otherObject instanceof Person)){
            return false;
        }

        //4蚀腿、將 otherObject 轉(zhuǎn)換成對(duì)應(yīng)的類(lèi)類(lèi)型變量
        Person other = (Person) otherObject;

        //5、最后對(duì)對(duì)象的屬性進(jìn)行比較扫外。使用 == 比較基本類(lèi)型唯咬,使用 equals 比較對(duì)象纱注。如果都相等則返回true,否則返回false
        //   使用 Objects 工具類(lèi)的 equals 方法防止比較的兩個(gè)對(duì)象有一個(gè)為 null而報(bào)錯(cuò)胆胰,因?yàn)?null.equals() 是會(huì)拋異常的
        return Objects.equals(this.pname,other.pname) && this.page == other.page;

        //6狞贱、注意如果是在子類(lèi)中定義equals,則要包含 super.equals(other)
        //return super.equals(other) && Objects.equals(this.pname,other.pname) && this.page == other.page;

    }

請(qǐng)注意蜀涨,無(wú)論何時(shí)重寫(xiě)此方法瞎嬉,通常都必須重寫(xiě)hashCode方法,以維護(hù)hashCode方法的一般約定厚柳,該方法聲明相等對(duì)象必須具有相同的哈希代碼氧枣。hashCode 也是 Object 類(lèi)中的方法,后面會(huì)詳細(xì)講解别垮。

5便监、getClass 方法

上面我們?cè)诮榻B equals 方法時(shí),介紹如果 equals 的語(yǔ)義在每個(gè)子類(lèi)中有所改變碳想,那么使用 getClass 檢測(cè)烧董,為什么這樣說(shuō)呢?

getClass()在 Object 類(lèi)中如下胧奔,作用是返回對(duì)象的運(yùn)行時(shí)類(lèi)逊移。

public final native Class<?> getClass();

這是一個(gè)用 native 關(guān)鍵字修飾的方法,關(guān)于 native 關(guān)鍵字的詳細(xì)介紹如下:http://www.cnblogs.com/ysocean/p/8476933.html

這里我們要知道用 native 修飾的方法我們不用考慮龙填,由操作系統(tǒng)幫我們實(shí)現(xiàn)胳泉,該方法的作用是返回一個(gè)對(duì)象的運(yùn)行時(shí)類(lèi),通過(guò)這個(gè)類(lèi)對(duì)象我們可以獲取該運(yùn)行時(shí)類(lèi)的相關(guān)屬性和方法岩遗。也就是Java中的反射扇商,各種通用的框架都是利用反射來(lái)實(shí)現(xiàn)的,這里我們不做詳細(xì)的描述宿礁。

這里詳細(xì)的介紹 getClass 方法返回的是一個(gè)對(duì)象的運(yùn)行時(shí)類(lèi)對(duì)象案铺,這該怎么理解呢?Java中還有一種這樣的用法窘拯,通過(guò) 類(lèi)名.class 獲取這個(gè)類(lèi)的類(lèi)對(duì)象 红且,這兩種用法有什么區(qū)別呢坝茎?

父類(lèi):Parent.class

public class Parent {}

子類(lèi):Son.class

public class Son extends Parent{}

測(cè)試:

@Test
public void testClass(){
    Parent p = new Son();
    System.out.println(p.getClass());
    System.out.println(Parent.class);
}

打印結(jié)果:

image

  
  結(jié)論:class 是一個(gè)類(lèi)的屬性涤姊,能獲取該類(lèi)編譯時(shí)的類(lèi)對(duì)象,而 getClass() 是一個(gè)類(lèi)的方法嗤放,它是獲取該類(lèi)運(yùn)行時(shí)的類(lèi)對(duì)象思喊。

還有一個(gè)需要大家注意的是,雖然Object類(lèi)中g(shù)etClass() 方法聲明是:public final native Class<?> getClass();返回的是一個(gè) Class<?>次酌,但是如下是能通過(guò)編譯的:

Class<? extends String> c = "".getClass();

也就是說(shuō)類(lèi)型為T(mén)的變量getClass方法的返回值類(lèi)型其實(shí)是Class<? extends T>而非getClass方法聲明中的Class<?>恨课。

這在官方文檔中也有說(shuō)明:https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#getClass--

6舆乔、hashCode 方法

hashCode 在 Object 類(lèi)中定義如下:

public native int hashCode();

這也是一個(gè)用 native 聲明的本地方法,作用是返回對(duì)象的散列碼剂公,是 int 類(lèi)型的數(shù)值希俩。

那么這個(gè)方法存在的意義是什么呢?

我們知道在Java 中有幾種集合類(lèi),比如 List,Set纲辽,還有 Map颜武,List集合一般是存放的元素是有序可重復(fù)的,Set 存放的元素則是無(wú)序不可重復(fù)的拖吼,而 Map 集合存放的是鍵值對(duì)鳞上。

前面我們說(shuō)過(guò)判斷一個(gè)元素是否相等可以通過(guò) equals 方法,沒(méi)增加一個(gè)元素吊档,那么我們就通過(guò) equals 方法判斷集合中的每一個(gè)元素是否重復(fù)篙议,但是如果集合中有10000個(gè)元素了,但我們新加入一個(gè)元素時(shí)怠硼,那就需要進(jìn)行10000次equals方法的調(diào)用鬼贱,這顯然效率很低。

于是拒名,Java 的集合設(shè)計(jì)者就采用了 哈希表 來(lái)實(shí)現(xiàn)吩愧。關(guān)于哈希表的數(shù)據(jù)結(jié)構(gòu)我有過(guò)介紹。哈希算法也稱(chēng)為散列算法增显,是將數(shù)據(jù)依特定算法產(chǎn)生的結(jié)果直接指定到一個(gè)地址上雁佳。這個(gè)結(jié)果就是由 hashCode 方法產(chǎn)生。這樣一來(lái)同云,當(dāng)集合要添加新的元素時(shí)糖权,先調(diào)用這個(gè)元素的 hashCode 方法,就一下子能定位到它應(yīng)該放置的物理位置上炸站。

①星澳、如果這個(gè)位置上沒(méi)有元素,它就可以直接存儲(chǔ)在這個(gè)位置上旱易,不用再進(jìn)行任何比較了禁偎;

②、如果這個(gè)位置上已經(jīng)有元素了阀坏,就調(diào)用它的equals方法與新元素進(jìn)行比較如暖,相同的話就不存了;

③忌堂、不相同的話盒至,也就是發(fā)生了Hash key相同導(dǎo)致沖突的情況,那么就在這個(gè)Hash key的地方產(chǎn)生一個(gè)鏈表,將所有產(chǎn)生相同HashCode的對(duì)象放到這個(gè)單鏈表上去枷遂,串在一起(很少出現(xiàn))樱衷。這樣一來(lái)實(shí)際調(diào)用equals方法的次數(shù)就大大降低了,幾乎只需要一兩次酒唉。
  


image

  這里有 A,B,C,D四個(gè)對(duì)象矩桂,分別通過(guò) hashCode 方法產(chǎn)生了三個(gè)值,注意 A 和 B 對(duì)象調(diào)用 hashCode 產(chǎn)生的值是相同的痪伦,即 A.hashCode() = B.hashCode() = 0x001,發(fā)生了哈希沖突耍鬓,這時(shí)候由于最先是插入了 A,在插入的B的時(shí)候流妻,我們發(fā)現(xiàn) B 是要插入到 A 所在的位置牲蜀,而 A 已經(jīng)插入了,這時(shí)候就通過(guò)調(diào)用 equals 方法判斷 A 和 B 是否相同绅这,如果相同就不插入 B涣达,如果不同則將 B 插入到 A 后面的位置。所以對(duì)于 equals 方法和 hashCode 方法有如下要求:

一证薇、hashCode 要求
 《忍Α①、在程序運(yùn)行時(shí)期間浑度,只要對(duì)象的(字段的)變化不會(huì)影響equals方法的決策結(jié)果寇窑,那么,在這個(gè)期間箩张,無(wú)論調(diào)用多少次hashCode甩骏,都必須返回同一個(gè)散列碼。

②先慷、通過(guò)equals調(diào)用返回true 的2個(gè)對(duì)象的hashCode一定一樣饮笛。

③、通過(guò)equasl返回false 的2個(gè)對(duì)象的散列碼不需要不同论熙,也就是他們的hashCode方法的返回值允許出現(xiàn)相同的情況福青。

因此我們可以得到如下推論:

兩個(gè)對(duì)象相等,其 hashCode 一定相同;

兩個(gè)對(duì)象不相等脓诡,其 hashCode 有可能相同;

hashCode 相同的兩個(gè)對(duì)象无午,不一定相等;

hashCode 不相同的兩個(gè)對(duì)象,一定不相等;

這四個(gè)推論通過(guò)上圖可以更好的理解祝谚。

可能會(huì)有人疑問(wèn)宪迟,對(duì)于不能重復(fù)的集合,為什么不直接通過(guò) hashCode 對(duì)于每個(gè)元素都產(chǎn)生唯一的值踊跟,如果重復(fù)就是相同的值踩验,這樣不就不需要調(diào)用 equals 方法來(lái)判斷是否相同了嗎?
  實(shí)際上對(duì)于元素不是很多的情況下商玫,直接通過(guò) hashCode 產(chǎn)生唯一的索引值箕憾,通過(guò)這個(gè)索引值能直接找到元素,而且還能判斷是否相同拳昌。比如數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)袭异,ID 是有序排列的,我們能通過(guò) ID 直接找到某個(gè)元素炬藤,如果新插入的元素 ID 已經(jīng)有了御铃,那就表示是重復(fù)數(shù)據(jù),這是很完美的辦法沈矿。但現(xiàn)實(shí)是存儲(chǔ)的元素很難有這樣的 ID 關(guān)鍵字上真,也就很難這種實(shí)現(xiàn) hashCode 的唯一算法,再者就算能實(shí)現(xiàn)羹膳,但是產(chǎn)生的 hashCode 碼是非常大的睡互,這會(huì)大的超過(guò) Java 所能表示的范圍,很占內(nèi)存空間陵像,所以也是不予考慮的就珠。

二、hashCode 編寫(xiě)指導(dǎo):
 ⌒延薄①妻怎、不同對(duì)象的hash碼應(yīng)該盡量不同,避免hash沖突泞歉,也就是算法獲得的元素要盡量均勻分布逼侦。

②、hash 值是一個(gè) int 類(lèi)型腰耙,在Java中占用 4 個(gè)字節(jié)偿洁,也就是 232 次方,要避免溢出沟优。

在 JDK 的 Integer類(lèi)涕滋,F(xiàn)loat 類(lèi),String 類(lèi)等都重寫(xiě)了 hashCode 方法挠阁,我們自定義對(duì)象也可以參考這些類(lèi)來(lái)寫(xiě)宾肺。

下面是 JDK String 類(lèi)的hashCode 源碼:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

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

再次提醒大家,對(duì)于 Map 集合侵俗,我們可以選取Java中的基本類(lèi)型锨用,還有引用類(lèi)型 String 作為 key,因?yàn)樗鼈兌及凑找?guī)范重寫(xiě)了 equals 方法和 hashCode 方法隘谣。但是如果你用自定義對(duì)象作為 key增拥,那么一定要覆寫(xiě) equals 方法和 hashCode 方法啄巧,不然會(huì)有意想不到的錯(cuò)誤產(chǎn)生。

7掌栅、toString 方法

該方法在 JDK 的源碼如下:

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

getClass().getName()是返回對(duì)象的全類(lèi)名(包含包名),Integer.toHexString(hashCode()) 是以16進(jìn)制無(wú)符號(hào)整數(shù)形式返回此哈希碼的字符串表示形式秩仆。

打印某個(gè)對(duì)象時(shí),默認(rèn)是調(diào)用 toString 方法猾封,比如 System.out.println(person),等價(jià)于 System.out.println(person.toString())

8澄耍、notify()/notifyAll()/wait()

這是用于多線程之間的通信方法,在后面講解多線程會(huì)詳細(xì)描述晌缘,這里就不做講解了齐莲。

protected void finalize() throws Throwable { }

該方法用于垃圾回收,一般由 JVM 自動(dòng)調(diào)用磷箕,一般不需要程序員去手動(dòng)調(diào)用該方法选酗。后面再講解 JVM 的時(shí)候會(huì)詳細(xì)展開(kāi)描述。

10岳枷、registerNatives 方法

該方法在 Object 類(lèi)中定義如下:

private static native void registerNatives();

這是一個(gè)本地方法星掰,在 native 介紹 中我們知道一個(gè)類(lèi)定義了本地方法后,想要調(diào)用操作系統(tǒng)的實(shí)現(xiàn)嫩舟,必須還要裝載本地庫(kù)氢烘,但是我們發(fā)現(xiàn)在 Object.class 類(lèi)中具有很多本地方法,但是卻沒(méi)有看到本地庫(kù)的載入代碼家厌。而且這是用 private 關(guān)鍵字聲明的播玖,在類(lèi)外面根本調(diào)用不了,我們接著往下看關(guān)于這個(gè)方法的類(lèi)似源碼:

    static {
        registerNatives();
    }

看到上面的代碼饭于,這就明白了吧蜀踏。靜態(tài)代碼塊就是一個(gè)類(lèi)在初始化過(guò)程中必定會(huì)執(zhí)行的內(nèi)容,所以在類(lèi)加載的時(shí)候是會(huì)執(zhí)行該方法的掰吕,通過(guò)該方法來(lái)注冊(cè)本地方法果覆。

參考文檔:https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html

本系列教程持續(xù)更新,可以微信搜索「 IT可樂(lè) 」第一時(shí)間閱讀殖熟【执回復(fù)《電子書(shū)》有我為大家特別刷選的書(shū)籍資料

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市菱属,隨后出現(xiàn)的幾起案子钳榨,更是在濱河造成了極大的恐慌,老刑警劉巖纽门,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薛耻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赏陵,警方通過(guò)查閱死者的電腦和手機(jī)饼齿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)饲漾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人缕溉,你說(shuō)我怎么就攤上這事考传。” “怎么了倒淫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)败玉。 經(jīng)常有香客問(wèn)我敌土,道長(zhǎng),這世上最難降的妖魔是什么运翼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任返干,我火速辦了婚禮,結(jié)果婚禮上血淌,老公的妹妹穿的比我還像新娘矩欠。我一直安慰自己,他們只是感情好悠夯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布癌淮。 她就那樣靜靜地躺著,像睡著了一般沦补。 火紅的嫁衣襯著肌膚如雪乳蓄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天夕膀,我揣著相機(jī)與錄音虚倒,去河邊找鬼。 笑死产舞,一個(gè)胖子當(dāng)著我的面吹牛魂奥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播易猫,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼耻煤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了准颓?” 一聲冷哼從身側(cè)響起违霞,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞬场,沒(méi)想到半個(gè)月后买鸽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贯被,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年眼五,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妆艘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡看幼,死狀恐怖批旺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诵姜,我是刑警寧澤汽煮,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站棚唆,受9級(jí)特大地震影響暇赤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宵凌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一鞋囊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞎惫,春花似錦溜腐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至乘寒,卻和暖如春矩肩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肃续。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工黍檩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人始锚。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓刽酱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親瞧捌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棵里,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350