Java根類Object的方法說明

Java中的Object類是所有類的父類,它提供了以下11個方法:

  1. public final native Class<?> getClass()
  2. public native int hashCode()
  3. public boolean equals(Object obj)
  4. protected native Object clone() throws CloneNotSupportedException
  5. public String toString()
  6. public final native void notify()
  7. public final native void notifyAll()
  8. public final native void wait(long timeout) throws InterruptedException
  9. public final void wait(long timeout, int nanos) throws InterruptedException
  10. public final void wait() throws InterruptedException
  11. protected void finalize() throws Throwable { }

下面我們一個個方法進(jìn)行分析,看這些方法到底有什么作用:

getClass方法

getClass方法是一個final方法,不允許子類重寫,并且也是一個native方法。

返回當(dāng)前運(yùn)行時對象的Class對象,注意這里是運(yùn)行時,比如以下代碼中n是一個Number類型的實(shí)例,但是java中數(shù)值默認(rèn)是Integer類型,所以getClass方法返回的是java.lang.Integer:

"str".getClass() // class java.lang.String
"str".getClass == String.class // true
Number n = 0;
Class<? extends Number> c = n.getClass(); // class java.lang.Integer
hashCode方法

hashCode方法也是一個native方法。 該方法返回對象的哈希碼,主要使用在哈希表中,比如JDK中的HashMap。

哈希碼的通用約定如下:

  1. 在java程序執(zhí)行過程中,在一個對象沒有被改變的前提下,無論這個對象被調(diào)用多少次,hashCode方法都會返回相同的整數(shù)值。對象的哈希碼沒有必要在不同的程序中保持相同的值。
  2. 如果2個對象使用equals方法進(jìn)行比較并且相同的話,那么這2個對象的hashCode方法的值也必須相等。
  3. 如果根據(jù)equals方法,得到兩個對象不相等,那么這2個對象的hashCode值不需要必須不相同。但是,不相等的對象的hashCode值不同的話可以提高哈希表的性能。

通常情況下,不同的對象產(chǎn)生的哈希碼是不同的。默認(rèn)情況下,對象的哈希碼是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實(shí)現(xiàn)的。

String的hashCode方法實(shí)現(xiàn)如下, 計算方法是 s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1],其中s[0]表示字符串的第一個字符,n表示字符串長度:

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;
}

比如”fo”的hashCode = 102 *31^1 + 111 = 3273, “foo”的hashCode = 102 *31^2 + 111 * 31^1 + 111 = 101574 (‘f’的ascii碼為102, ‘o’的ascii碼為111)

hashCode在哈希表HashMap中的應(yīng)用:

// Student類,只重寫了hashCode方法
public static class Student {

    private String name;
    private int age;

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

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

Map<Student, String> map = new HashMap<Student, String>();
Student stu1 = new Student("fo", 11);
Student stu2 = new Student("fo", 22);
map.put(stu1, "fo");
map.put(stu2, "fo");

上面這段代碼中,map中有2個元素stu1和stu2。但是這2個元素是在哈希表中的同一個數(shù)組項(xiàng)中的位置,也就是在同一串鏈表中。 但是為什么stu1和stu2的hashCode相同,但是兩條元素都插到map里了,這是因?yàn)閙ap判斷重復(fù)數(shù)據(jù)的條件是 兩個對象的哈希碼相同并且(兩個對象是同一個對象或者兩個對象相等[equals為true])。 所以再給Student重寫equals方法,并且只比較name的話,這樣map就只有1個元素了。

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return this.name.equals(student.name);
}

這個例子直接說明了hashCode中通用約定的第三點(diǎn):

第三點(diǎn):如果根據(jù)equals方法,得到兩個對象不相等,那么這2個對象的hashCode值不需要必須不相同。但是,不相等的對象的hashCode值不同的話可以提高哈希表的性能。 –> 上面例子一開始沒有重寫equals方法,導(dǎo)致兩個對象不相等,但是這兩個對象的hashCode值一樣,所以導(dǎo)致這兩個對象在同一串鏈表中,影響性能。

當(dāng)然,還有第三種情況,那就是equals方法相等,但是hashCode的值不相等。

這種情況也就是違反了通用約定的第二點(diǎn):

第二點(diǎn):如果2個對象使用equals方法進(jìn)行比較并且相同的話,那么這2個對象的hashCode方法的值也必須相等。 違反這一點(diǎn)產(chǎn)生的后果就是如果一個stu1實(shí)例是Student(“fo”, 11),stu2實(shí)例是Student(“fo”, 11),那么這2個實(shí)例是相等的,但是他們的hashCode不一樣,這樣是導(dǎo)致哈希表中都會存入stu1實(shí)例和stu2實(shí)例,但是實(shí)際情況下,stu1和stu2是重復(fù)數(shù)據(jù),只允許存在一條數(shù)據(jù)在哈希表中。所以這一點(diǎn)是非常重點(diǎn)的,再強(qiáng)調(diào)一下:如果2個對象的equals方法相等,那么他們的hashCode值也必須相等,反之,如果2個對象hashCode值相等,但是equals不相等,這樣會影響性能,所以還是建議2個方法都一起重寫。

equals方法

比較兩個對象是否相等。Object類的默認(rèn)實(shí)現(xiàn),即比較2個對象的內(nèi)存地址是否相等:

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

equals方法在非空對象引用上的特性:

  1. reflexive,自反性。任何非空引用值x,對于x.equals(x)必須返回true
  2. symmetric,對稱性。任何非空引用值x和y,如果x.equals(y)為true,那么y.equals(x)也必須為true
  3. transitive,傳遞性。任何非空引用值x、y和z,如果x.equals(y)為true并且y.equals(z)為true,那么x.equals(z)也必定為true
  4. consistent,一致性。任何非空引用值x和y,多次調(diào)用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改
  5. 對于任何非空引用值 x,x.equals(null) 都應(yīng)返回 false

Object類的equals方法對于任何非空引用值x和y,當(dāng)x和y引用同一個對象時,此方法才返回true。這個也就是我們常說的地址相等。

注意點(diǎn):如果重寫了equals方法,通常有必要重寫hashCode方法,這點(diǎn)已經(jīng)在hashCode方法中說明了。

clone方法

創(chuàng)建并返回當(dāng)前對象的一份拷貝。一般情況下,對于任何對象 x,表達(dá)式 x.clone() != x 為true,x.clone().getClass() == x.getClass() 也為true。

Object類的clone方法是一個protected的native方法。

由于Object本身沒有實(shí)現(xiàn)Cloneable接口,所以不重寫clone方法并且進(jìn)行調(diào)用的話會發(fā)生CloneNotSupportedException異常。

toString方法

Object對象的默認(rèn)實(shí)現(xiàn),即輸出類的名字@實(shí)例的哈希碼的16進(jìn)制:

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

toString方法的結(jié)果應(yīng)該是一個簡明但易于讀懂的字符串。建議Object所有的子類都重寫這個方法。

notify方法

notify方法是一個native方法,并且也是final的,不允許子類重寫。

喚醒一個在此對象監(jiān)視器上等待的線程(監(jiān)視器相當(dāng)于就是鎖的概念)。如果所有的線程都在此對象上等待,那么只會選擇一個線程。選擇是任意性的,并在對實(shí)現(xiàn)做出決定時發(fā)生。一個線程在對象監(jiān)視器上等待可以調(diào)用wait方法。

直到當(dāng)前線程放棄對象上的鎖之后,被喚醒的線程才可以繼續(xù)處理。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進(jìn)行競爭。例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權(quán)或劣勢。

notify方法只能被作為此對象監(jiān)視器的所有者的線程來調(diào)用。一個線程要想成為對象監(jiān)視器的所有者,可以使用以下3種方法:

  1. 執(zhí)行對象的同步實(shí)例方法
  2. 使用synchronized內(nèi)置鎖
  3. 對于Class類型的對象,執(zhí)行同步靜態(tài)方法

一次只能有一個線程擁有對象的監(jiān)視器。

如果當(dāng)前線程不是此對象監(jiān)視器的所有者的話會拋出IllegalMonitorStateException異常

注意點(diǎn):

因?yàn)閚otify只能在擁有對象監(jiān)視器的所有者線程中調(diào)用,否則會拋出IllegalMonitorStateException異常

notifyAll方法

跟notify一樣,唯一的區(qū)別就是會喚醒在此對象監(jiān)視器上等待的所有線程,而不是一個線程。

同樣,如果當(dāng)前線程不是對象監(jiān)視器的所有者,那么調(diào)用notifyAll同樣會發(fā)生IllegalMonitorStateException異常。

以下這段代碼直接調(diào)用notify或者notifyAll方法會發(fā)生IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用這兩個方法需要當(dāng)前線程是對象監(jiān)視器的所有者:

Factory factory = new Factory();
factory.notify();
factory.notifyAll();

wait(long timeout) throws InterruptedException方法

wait(long timeout)方法同樣是一個native方法,并且也是final的,不允許子類重寫。

wait方法會讓當(dāng)前線程等待直到另外一個線程調(diào)用對象的notify或notifyAll方法,或者超過參數(shù)設(shè)置的timeout超時時間。

跟notify和notifyAll方法一樣,當(dāng)前線程必須是此對象的監(jiān)視器所有者,否則還是會發(fā)生IllegalMonitorStateException異常。

wait方法會讓當(dāng)前線程(我們先叫做線程T)將其自身放置在對象的等待集中,并且放棄該對象上的所有同步要求。出于線程調(diào)度目的,線程T是不可用并處于休眠狀態(tài),直到發(fā)生以下四件事中的任意一件:

  1. 其他某個線程調(diào)用此對象的notify方法,并且線程T碰巧被任選為被喚醒的線程
  2. 其他某個線程調(diào)用此對象的notifyAll方法
  3. 其他某個線程調(diào)用Thread.interrupt方法中斷線程T
  4. 時間到了參數(shù)設(shè)置的超時時間。如果timeout參數(shù)為0,則不會超時,會一直進(jìn)行等待

所以可以理解wait方法相當(dāng)于放棄了當(dāng)前線程對對象監(jiān)視器的所有者(也就是說釋放了對象的鎖)

之后,線程T會被等待集中被移除,并且重新進(jìn)行線程調(diào)度。然后,該線程以常規(guī)方式與其他線程競爭,以獲得在該對象上同步的權(quán)利;一旦獲得對該對象的控制權(quán),該對象上的所有其同步聲明都將被恢復(fù)到以前的狀態(tài),這就是調(diào)用wait方法時的情況。然后,線程T從wait方法的調(diào)用中返回。所以,從wait方法返回時,該對象和線程T的同步狀態(tài)與調(diào)用wait方法時的情況完全相同。

在沒有被通知、中斷或超時的情況下,線程還可以喚醒一個所謂的虛假喚醒 (spurious wakeup)。雖然這種情況在實(shí)踐中很少發(fā)生,但是應(yīng)用程序必須通過以下方式防止其發(fā)生,即對應(yīng)該導(dǎo)致該線程被提醒的條件進(jìn)行測試,如果不滿足該條件,則繼續(xù)等待。換句話說,等待應(yīng)總是發(fā)生在循環(huán)中,如下面的示例:

synchronized (obj) {
    while (<condition does not hold>)
        obj.wait(timeout);
        ... // Perform action appropriate to condition
}

如果當(dāng)前線程在等待之前或在等待時被任何線程中斷,則會拋出InterruptedException異常。在按上述形式恢復(fù)此對象的鎖定狀態(tài)時才會拋出此異常。

wait(long timeout, int nanos) throws InterruptedException方法

跟wait(long timeout)方法類似,多了一個nanos參數(shù),這個參數(shù)表示額外時間(以毫微秒為單位,范圍是 0-999999)。 所以超時的時間還需要加上nanos毫秒。

需要注意的是 wait(0, 0)和wait(0)效果是一樣的,即一直等待。

wait() throws InterruptedException方法

跟之前的2個wait方法一樣,只不過該方法一直等待,沒有超時時間這個概念。

以下這段代碼直接調(diào)用wait方法會發(fā)生IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用wait方法需要當(dāng)前線程是對象監(jiān)視器的所有者:

Factory factory = new Factory();
factory.wait();

一般情況下,wait方法和notify方法會一起使用的,wait方法阻塞當(dāng)前線程,notify方法喚醒當(dāng)前線程,一個使用wait和notify方法的生產(chǎn)者消費(fèi)者例子代碼如下:

public class WaitNotifyTest {

    public static void main(String[] args) {
        Factory factory = new Factory();
        new Thread(new Producer(factory, 5)).start();
        new Thread(new Producer(factory, 5)).start();
        new Thread(new Producer(factory, 20)).start();
        new Thread(new Producer(factory, 30)).start();
        new Thread(new Consumer(factory, 10)).start();
        new Thread(new Consumer(factory, 20)).start();
        new Thread(new Consumer(factory, 5)).start();
        new Thread(new Consumer(factory, 5)).start();
        new Thread(new Consumer(factory, 20)).start();
    }

}

class Factory {

    public static final Integer MAX_NUM = 50;

    private int currentNum = 0;

    public void consume(int num) throws InterruptedException {
        synchronized (this) {
            while(currentNum - num < 0) {
                this.wait();
            }
            currentNum -= num;
            System.out.println("consume " + num + ", left: " + currentNum);
            this.notifyAll();
        }
    }

    public void produce(int num) throws InterruptedException {
        synchronized (this) {
            while(currentNum + num > MAX_NUM) {
                this.wait();
            }
            currentNum += num;
            System.out.println("produce " + num + ", left: " + currentNum);
            this.notifyAll();
        }
    }

}

class Producer implements Runnable {
    private Factory factory;
    private int num;
    public Producer(Factory factory, int num) {
        this.factory = factory;
        this.num = num;
    }
    @Override
    public void run() {
        try {
            factory.produce(num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Consumer implements Runnable {
    private Factory factory;
    private int num;
    public Consumer(Factory factory, int num) {
        this.factory = factory;
        this.num = num;
    }
    @Override
    public void run() {
        try {
            factory.consume(num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意的是Factory類的produce和consume方法都將Factory實(shí)例鎖住了,鎖住之后線程就成為了對象監(jiān)視器的所有者,然后才能調(diào)用wait和notify方法。

輸出:

produce 5, left: 5
produce 20, left: 25
produce 5, left: 30
consume 10, left: 20
produce 30, left: 50
consume 20, left: 30
consume 5, left: 25
consume 5, left: 20
consume 20, left: 0

finalize方法

finalize方法是一個protected方法,Object類的默認(rèn)實(shí)現(xiàn)是不進(jìn)行任何操作。

該方法的作用是實(shí)例被垃圾回收器回收的時候觸發(fā)的操作,就好比 “死前的最后一波掙扎”。

直接寫個弱引用例子:

Car car = new Car(9999, "black");
WeakReference<Car> carWeakReference = new WeakReference<Car>(car);

int i = 0;
while(true) {
    if(carWeakReference.get() != null) {
        i++;
        System.out.println("Object is alive for "+i+" loops - "+carWeakReference);
    } else {
        System.out.println("Object has been collected.");
        break;
    }
}

class Car {
    private double price;
    private String colour;

    public Car(double price, String colour){
        this.price = price;
        this.colour = colour;
    }

    // get set method

    @Override
    protected void finalize() throws Throwable {
        System.out.println("i will be destroyed");
    }
}

輸出:

....
Object is alive for 26417 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26418 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26419 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26420 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26421 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26422 loops - java.lang.ref.WeakReference@7c2f1622
Object has been collected.
i will be destroyed
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,688評論 18 399
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,257評論 0 62
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,798評論 11 349
  • 相關(guān)概念 面向?qū)ο蟮娜齻€特征 封裝,繼承,多態(tài).這個應(yīng)該是人人皆知.有時候也會加上抽象. 多態(tài)的好處 允許不同類對...
    東經(jīng)315度閱讀 2,192評論 0 8
  • 讀到今天依然懵懂,無從開口,無處下手…… 心=天理=良知=善;存天理就是存良知存善保持本心,去人欲就是去外物干擾去...
    笑笑寶閱讀 354評論 0 5

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