Java知識點系列:java.lang.Object

Object類位於java.lang包中,java.lang包包含着Java最基礎和核心的類,在編譯時會自動導入。Object類沒有定義屬性,一共有13個方法,具體的類定義結構以下圖:java

1.類構造器public Object();算法

大部分狀況下,Java中經過形如 new A(args..)形式建立一個屬於該類型的對象。其中A便是類名,A(args..)即此類定義中相對應的構造函數。經過此種形式建立的對象都是經過類中的構造函數完成。爲體現此特性,Java中規定:在類定義過程當中,對於未定義構造函數的類,默認會有一個無參數的構造函數,做爲全部類的基類,Object類天然要反映出此特性,在源碼中,未給出Object類構造函數定義,但實際上,此構造函數是存在的。多線程

固然,並非全部的類都是經過此種方式去構建,也天然的,並非全部的類構造函數都是public。ide

2.private static native void registerNatives();函數

registerNatives函數前面有native關鍵字修飾,Java中,用native關鍵字修飾的函數代表該方法的實現並非在Java中去完成,而是由C/C++去完成,並被編譯成了.dll,由Java去調用。方法的具體實現體在dll文件中,對於不一樣平臺,其具體實現應該有所不一樣。用native修飾,即表示操做系統,須要提供此方法,Java自己須要使用。具體到registerNatives()方法自己,其主要做用是將C/C++中的方法映射到Java中的native方法,實現方法命名的解耦。性能

既然如此,可能有人會問,registerNatives()修飾符爲private,且並無執行,做用何以達到?其實,在Java源碼中,此方法的聲明後有緊接着一段靜態代碼塊:ui

1 private static native void registerNatives();
2 static {
3     registerNatives();
4 }
 

3.protected native Object clone() throws CloneNotSupportedException;this

看,clone()方法又是一個被聲明爲native的方法,所以,咱們知道了clone()方法並非Java的原生方法,具體的實現是有C/C++完成的。clone英文翻譯爲"克隆",其目的是建立並返回此對象的一個副本。形象點理解,這有一輛科魯茲,你看着不錯,想要個如出一轍的。你調用此方法便可像變魔術同樣變出一輛如出一轍的科魯茲出來。配置同樣,長相同樣。但今後刻起,原來的那輛科魯茲若是進行了新的裝飾,與你克隆出來的這輛科魯茲沒有任何關係了。你克隆出來的對象變不變徹底在於你對克隆出來的科魯茲有沒有進行過什麼操做了。Java術語表述爲:clone函數返回的是一個引用,指向的是新的clone出來的對象,此對象與原對象分別佔用不一樣的堆空間。編碼

明白了clone的含義後,接下來看看若是調用clone()函數對象進行此克隆操做。操作系統

首先看一下下面的這個例子:

複製代碼

1 package com.corn.objectsummary;
 2 
 3 import com.corn.Person;
 4 
 5 public class ObjectTest {
 6 
 7     public static void main(String[] args) {
 8 
 9         Object o1 = new Object();
10         // The method clone() from the type Object is not visible
11         Object clone = o1.clone();
12     }
13 
14 }

複製代碼

例子很簡單,在main()方法中,new一個Oject對象後,想直接調用此對象的clone方法克隆一個對象,可是出現錯誤提示:"The method clone() from the type Object is not visible"

why? 根據提示,第一反應是ObjectTest類中定義的Oject對象沒法訪問其clone()方法。回到Object類中clone()方法的定義,能夠看到其被聲明爲protected,估計問題就在這上面了,protected修飾的屬性或方法表示:在同一個包內或者不一樣包的子類能夠訪問。顯然,Object類與ObjectTest類在不一樣的包中,可是ObjectTest繼承自Object,是Object類的子類,因而,如今卻出現子類中經過Object引用不能訪問protected方法,緣由在於對"不一樣包中的子類能夠訪問"沒有正確理解。

"不一樣包中的子類能夠訪問",是指當兩個類不在同一個包中的時候,繼承自父類的子類內部且主調(調用者)爲子類的引用時才能訪問父類用protected修飾的成員(屬性/方法)。 在子類內部,主調爲父類的引用時並不能訪問此protected修飾的成員。!(super關鍵字除外)

因而,上例改爲以下形式,咱們發現,能夠正常編譯:

複製代碼

1 package com.corn.objectsummary;
 2 
 3 
 4 public class ObjectTest {
 5 
 6     public static void main(String[] args) {
 7         ObjectTest ot1 = new ObjectTest();
 8 
 9         try {
10             ObjectTest ot2 = (ObjectTest) ot1.clone();
11         } catch (CloneNotSupportedException e) {
12             // TODO Auto-generated catch block
13             e.printStackTrace();
14         }
15     }
16 
17 }

複製代碼

是的,由於此時的主調已是子類的引用了。

上述代碼在運行過程當中會拋出"java.lang.CloneNotSupportedException",代表clone()方法並未正確執行完畢,問題的緣由在與Java中的語法規定:

clone()的正確調用是須要實現Cloneable接口,若是沒有實現Cloneable接口,而且子類直接調用Object類的clone()方法,則會拋出CloneNotSupportedException異常。

Cloneable接口僅是一個表示接口,接口自己不包含任何方法,用來指示Object.clone()能夠合法的被子類引用所調用。

因而,上述代碼改爲以下形式,便可正確指定clone()方法以實現克隆。

複製代碼

1 package com.corn.objectsummary;
 2 
 3 public class ObjectTest implements Cloneable {
 4 
 5     public static void main(String[] args) {
 6 
 7         ObjectTest ot1 = new ObjectTest();
 8 
 9         try {
10             ObjectTest ot2 = (ObjectTest) ot1.clone();
11             System.out.println("ot2:" + ot2);
12         } catch (CloneNotSupportedException e) {
13             // TODO Auto-generated catch block
14             e.printStackTrace();
15         }
16     }
17 
18 }

複製代碼

4.public final native Class<?> getClass();

getClass()也是一個native方法,返回的是此Object對象的類對象/運行時類對象Class<?>。效果與Object.class相同。

首先解釋下"類對象"的概念:在Java中,類是是對具備一組相同特徵或行爲的實例的抽象並進行描述,對象則是此類所描述的特徵或行爲的具體實例。做爲概念層次的類,其自己也具備某些共同的特性,如都具備類名稱、由類加載器去加載,都具備包,具備父類,屬性和方法等。因而,Java中有專門定義了一個類,Class,去描述其餘類所具備的這些特性,所以,今後角度去看,類自己也都是屬於Class類的對象。爲與常常意義上的對象相區分,在此稱之爲"類對象"。

此處主要大量涉及到Java中的反射知識,關於反射相關知識後續也會給出相關博文。

 

5.public boolean equals(Object obj);

==與equals在Java中常常被使用,你們也都知道==與equals的區別:

==表示的是變量值完成相同(對於基礎類型,地址中存儲的是值,引用類型則存儲指向實際對象的地址);

equals表示的是對象的內容徹底相同,此處的內容多指對象的特徵/屬性。

實際上,上面說法是不嚴謹的,更多的只是常見於String類中。首先看一下Object類中關於equals()方法的定義:

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

因而可知,Object原生的equals()方法內部調用的正是==,與==具備相同的含義。既然如此,爲何還要定義此equals()方法?

equlas()方法的正確理解應該是:判斷兩個對象是否相等。那麼判斷對象相等的標尺又是什麼?

如上,在object類中,此標尺即爲==。固然,這個標尺不是固定的,其餘類中能夠按照實際的須要對此標尺含義進行重定義。如String類中則是依據字符串內容是否相等來重定義了此標尺含義。如此能夠增長類的功能型和實際編碼的靈活性。固然了,若是自定義的類沒有重寫equals()方法來從新定義此標尺,那麼默認的將是其父類的equals(),直到object基類。

以下場景的實際業務需求,對於User bean,由實際的業務需求可知當屬性uid相同時,表示的是同一個User,即兩個User對象相等。則能夠重寫equals以重定義User對象相等的標尺。

複製代碼

1 package com.corn.objectsummary;
 2 
 3 public class User {
 4 
 5     private int uid;
 6     private String name;
 7     private int age;
 8 
 9     public int getUid() {
10         return uid;
11     }
12 
13     public void setUid(int uid) {
14         this.uid = uid;
15     }
16 
17     protected String getName() {
18         return name;
19     }
20 
21     public void setName(String name) {
22         this.name = name;
23     }
24 
25     public int getAge() {
26         return age;
27     }
28 
29     public void setAge(int age) {
30         this.age = age;
31     }
32 
33     @Override
34     public boolean equals(Object obj) {
35         if (obj == null || !(obj instanceof User)) {
36             return false;
37         }
38         if (((User) obj).getUid() == this.getUid()) {
39             return true;
40         }
41         return false;
42     }
43 }

複製代碼

複製代碼

1 package com.corn.objectsummary;
 2 
 3 public class ObjectTest implements Cloneable {
 4 
 5     public static void main(String[] args) {
 6         User u1 = new User();
 7         u1.setUid(111);
 8         u1.setName("張三");
 9 
10         User u2 = new User();
11         u2.setUid(111);
12         u2.setName("張三丰");
13 
14         System.out.println(u1.equals(u2)); //返回true
15     }
16 
17 }

複製代碼

ObjectTest中打印出true,由於User類定義中重寫了equals()方法,這很好理解,極可能張三是一我的小名,張三丰纔是其大名,判斷這兩我的是否是同一我的,這時只用判斷uid是否相同便可。

如上重寫equals方法表面上看上去是能夠了,實則否則。由於它破壞了Java中的約定:重寫equals()方法必須重寫hasCode()方法。

 

6.public native int hashCode();

hashCode()方法返回一個整形數值,表示該對象的哈希碼值。

hashCode()具備以下約定:

1).在Java應用程序程序執行期間,對於同一對象屢次調用hashCode()方法時,其返回的哈希碼是相同的,前提是將對象進行equals比較時所用的標尺信息未作修改。在Java應用程序的一次執行到另一次執行,同一對象的hashCode()返回的哈希碼無須保持一致;

2).若是兩個對象相等(依據:調用equals()方法),那麼這兩個對象調用hashCode()返回的哈希碼也必須相等;

3).反之,兩個對象調用hasCode()返回的哈希碼相等,這兩個對象不必定相等。

即嚴格的數學邏輯表示爲: 兩個對象相等 <=>  equals()相等  => hashCode()相等。所以,重寫equlas()方法必須重寫hashCode()方法,以保證此邏輯嚴格成立,同時能夠推理出:hasCode()不相等 => equals()不相等 <=> 兩個對象不相等。

可能有人在此產生疑問:既然比較兩個對象是否相等的惟一條件(也是衝要條件)是equals,那麼爲何還要弄出一個hashCode(),而且進行如此約定,弄得這麼麻煩?

其實,這主要體如今hashCode()方法的做用上,其主要用於加強哈希表的性能。

以集合類中,以Set爲例,當新加一個對象時,須要判斷現有集合中是否已經存在與此對象相等的對象,若是沒有hashCode()方法,須要將Set進行一次遍歷,並逐一用equals()方法判斷兩個對象是否相等,此種算法時間複雜度爲o(n)。經過藉助於hasCode方法,先計算出即將新加入對象的哈希碼,而後根據哈希算法計算出此對象的位置,直接判斷此位置上是否已有對象便可。(注:Set的底層用的是Map的原理實現)

在此須要糾正一個理解上的誤區:對象的hashCode()返回的不是對象所在的物理內存地址。甚至也不必定是對象的邏輯地址,hashCode()相同的兩個對象,不必定相等,換言之,不相等的兩個對象,hashCode()返回的哈希碼可能相同。

所以,在上述代碼中,重寫了equals()方法後,須要重寫hashCode()方法。

複製代碼

1 package com.corn.objectsummary;
 2 
 3 public class User {
 4 
 5     private int uid;
 6     private String name;
 7     private int age;
 8 
 9     public int getUid() {
10         return uid;
11     }
12 
13     public void setUid(int uid) {
14         this.uid = uid;
15     }
16 
17     protected String getName() {
18         return name;
19     }
20 
21     public void setName(String name) {
22         this.name = name;
23     }
24 
25     public int getAge() {
26         return age;
27     }
28 
29     public void setAge(int age) {
30         this.age = age;
31     }
32 
33     @Override
34     public boolean equals(Object obj) {
35         if (obj == null || !(obj instanceof User)) {
36             return false;
37         }
38         if (((User) obj).getUid() == this.getUid()) {
39             return true;
40         }
41         return false;
42     }
43 
44     @Override
45     public int hashCode() {
46         int result = 17;
47         result = 31 * result + this.getUid();
48         return result;
49     }
50 }

複製代碼

注:上述hashCode()的重寫中出現了result*31,是由於result*31 = (result<<5) - result。之因此選擇31,是由於左移運算和減運算計算效率遠大於乘法運算。固然,也能夠選擇其餘數字。

 

7.public String toString();

toString()方法返回該對象的字符串表示。先看一下Object中的具體方法體:

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

toString()方法相信你們都常常用到,即便沒有顯式調用,但當咱們使用System.out.println(obj)時,其內部也是經過toString()來實現的。

getClass()返回對象的類對象,getClassName()以String形式返回類對象的名稱(含包名)。Integer.toHexString(hashCode())則是以對象的哈希碼爲實參,以16進制無符號整數形式返回此哈希碼的字符串表示形式。

如上例中的u1的哈希碼是638,則對應的16進製爲27e,調用toString()方法返回的結果爲:com.corn.objectsummary.User@27e。

所以:toString()是由對象的類型和其哈希碼惟一肯定,同一類型但不相等的兩個對象分別調用toString()方法返回的結果可能相同。

8/9/10/11/12. wait(...) / notify() / notifyAll()

一說到wait(...) / notify() | notifyAll()幾個方法,首先想到的是線程。確實,這幾個方法主要用於java多線程之間的協做。先具體看下這幾個方法的主要含義:

wait():調用此方法所在的當前線程等待,直到在其餘線程上調用此方法的主調(某一對象)的notify()/notifyAll()方法。

wait(long timeout)/wait(long timeout, int nanos):調用此方法所在的當前線程等待,直到在其餘線程上調用此方法的主調(某一對象)的notisfy()/notisfyAll()方法,或超過指定的超時時間量。

notify()/notifyAll():喚醒在此對象監視器上等待的單個線程/全部線程。

wait(...) / notify() | notifyAll()通常狀況下都是配套使用。下面來看一個簡單的例子:

複製代碼

1 package com.qqyumidi;
 2 
 3 public class ThreadTest {
 4 
 5     /**
 6      * @param args
 7      */
 8     public static void main(String[] args) {
 9         // TODO Auto-generated method stub
10         MyRunnable r = new MyRunnable();
11         Thread t = new Thread(r);
12         t.start();
13         synchronized (r) {
14             try {
15                 System.out.println("main thread 等待t線程執行完");
16                 r.wait();
17                 System.out.println("被notity喚醒,得以繼續執行");
18             } catch (InterruptedException e) {
19                 // TODO Auto-generated catch block
20                 e.printStackTrace();
21                 System.out.println("main thread 本想等待,但被意外打斷了");
22             }
23             System.out.println("線程t執行相加結果" + r.getTotal());
24         }
25     }
26 }
27 
28 class MyRunnable implements Runnable {
29     private int total;
30 
31     @Override
32     public void run() {
33         // TODO Auto-generated method stub
34         synchronized (this) {
35             System.out.println("Thread name is:" + Thread.currentThread().getName());
36             for (int i = 0; i < 10; i++) {
37                 total += i;
38             }
39             notify();
40             System.out.println("執行notif後同步代碼塊中依然能夠繼續執行直至完畢");
41         }
42         System.out.println("執行notif後且同步代碼塊外的代碼執行時機取決於線程調度");
43     }
44 
45     public int getTotal() {
46         return total;
47     }
48 }

複製代碼

運行結果爲:

複製代碼

1 main thread 等待t線程執行完
2 Thread name is:Thread-0
3 執行notif後同步代碼塊中依然能夠繼續執行直至完畢
4 執行notif後且同步代碼塊外的代碼執行時機取決於線程調度  //此行輸出位置有具體的JVM線程調度決定,有可能最後執行
5 被notity喚醒,得以繼續執行
6 線程t執行相加結果45

複製代碼

 

既然是做用於多線程中,爲何倒是Object這個基類所具備的方法?緣由在於理論上任何對象均可以視爲線程同步中的監聽器,且wait(...)/notify()|notifyAll()方法只能在同步代碼塊中才能使用。

 從上述例子的輸出結果中能夠得出以下結論:

一、wait(...)方法調用後當前線程將當即阻塞,且適當其所持有的同步代碼塊中的鎖,直到被喚醒或超時或打斷後且從新獲取到鎖後才能繼續執行;

二、notify()/notifyAll()方法調用後,其所在線程不會當即釋放所持有的鎖,直到其所在同步代碼塊中的代碼執行完畢,此時釋放鎖,所以,若是其同步代碼塊後還有代碼,其執行則依賴於JVM的線程調度。

在Java源碼中,能夠看到wait()具體定義以下:

1  public final void wait() throws InterruptedException {
2      wait(0);
3  }

且wait(long timeout, int nanos)方法定義內部實質上也是經過調用wait(long timeout)完成。而wait(long timeout)是一個native方法。所以,wait(...)方法本質上都是native方式實現。

notify()/notifyAll()方法也都是native方法。

Java中線程具備較多的知識點,是一塊比較大且重要的知識點。後期會有博文專門針對Java多線程做出詳細總結。此處再也不細述。

 

13. protected void finalize();

finalize方法主要與Java垃圾回收機制有關。首先咱們看一下finalized方法在Object中的具體定義:

1 protected void finalize() throws Throwable { }

咱們發現Object類中finalize方法被定義成一個空方法,爲何要如此定義呢?finalize方法的調用時機是怎麼樣的呢?

首先,Object中定義finalize方法代表Java中每個對象都將具備finalize這種行爲,其具體調用時機在:JVM準備對此對形象所佔用的內存空間進行垃圾回收前,將被調用。由此能夠看出,此方法並非由咱們主動去調用的(雖然能夠主動去調用,此時與其餘自定義方法無異)。

相關文章
相關標籤/搜索