1. 程式人生 > >[ 轉載 ] Java基礎10--關於Object類下所有方法的簡單解析

[ 轉載 ] Java基礎10--關於Object類下所有方法的簡單解析

zed final關鍵字 pro target 解釋 temp cat turn syn

關於Object類下所有方法的簡單解析

類Object是類層次結構的根類,是每一個類的父類,所有的對象包括數組,String,Integer等包裝類,所以了解Object是很有必要的,話不多說,我們直接來看jdk的源碼,開始我們的分析之路

1.hashcode()

public native int hashCode();//native說明跟機器有關,跟對象的地址有關

如果我們新建一個類,而hashcode沒有被重寫的話,那麽hashcode返回的值只於對象的地址有關,如果hashcode被重寫了,那麽就另當別論了,但是如果我們重寫了equals()的話,那麽hashcode(),一定要重寫,至於為什麽要重寫的話,請參考我這篇博客:http://www.cnblogs.com/yinbiao/p/8110196.html

現在我們來看一下JDk API文檔裏面關於hashcode的說明

hashCode 的常規協定是:

  • 在 Java 應用程序執行期間,在對同一對象多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。從某一應用程序的一次執行到同一應用程序的另一次執行,該整數無需保持一致。
  • 如果根據 equals(Object) 方法,兩個對象是相等的,那麽對這兩個對象中的每個對象調用 hashCode 方法都必須生成相同的整數結果。
  • 如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麽對這兩個對象中的任一對象上調用 hashCode
    方法 要求一定生成不同的整數結果。但是,程序員應該意識到,為不相等的對象生成不同整數結果可以提高哈希表的性能。

2.equals()

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

可以看到如果equals()如果沒有被重寫的話,比較的是對象的地址,String,Integer等包裝類裏面都重寫了該方法,例如String類的equals()方法是比較對象的內容,也就是字符串的,而不是地址

JDK API文檔對equals()說明如下:

equals 方法在非空對象引用上實現相等關系:

  • 自反性:對於任何非空引用值 x
    x.equals(x) 都應返回 true
  • 對稱性:對於任何非空引用值 xy,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true
  • 傳遞性:對於任何非空引用值 xyz,如果 x.equals(y) 返回 true,並且 y.equals(z) 返回 true,那麽 x.equals(z) 應返回 true
  • 一致性:對於任何非空引用值 xy,多次調用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改。
  • 對於任何非空引用值 xx.equals(null) 都應返回 false

同時這些性質也是我們在重寫equals()需要滿足的要求

3.getClass()

我們先來看一下JDK API文檔對getclass的說明:

返回此 Object 的運行時類。返回的 Class 對象是由所表示類的 static synchronized 方法鎖定的對象。

再看一下源碼:

public final Class<?> getClass() {
return shadow$_klass_; }

註意到final關鍵字,說明這個方法是不能被Object的子類重寫的,我們在類中調用的getclass()方法都是調用的Object下面的,且文檔對此的說明是返回運行時的對象,舉個栗子:

技術分享圖片
 1 import java.util.Date;
 2 public  class Test extends Date
 3 {
 4       public void test()
 5       {
 6               System.out.println(super.getClass().getName());
 7       }
 8       public static void main(String[] args) 
 9       {
10               new Test().test();
11       }
12 }
技術分享圖片

輸出:Test

有沒有覺得很奇怪?輸出的竟然是Test,其實一點也不奇怪,我們調用的getClass()是Object的,且它不能被重寫,返回的是類運行時的對象,在代碼中運行時的類是Test,雖然Test繼承與Date,但是如果我們想返回的是Date類,那麽我們運行時的對象應該是Date:Date date = new Date();,現在運行時的類就是Date,返回的就是Date。

那麽現在又有一個問題,getClass()和getName(),好像他們的輸出值都是一樣的,那是不是他們就沒有區別呢?不是的,話不多說,直接擼源碼:

public String getName() {

if (name == null) name = getName0(); return name; } public final native Class<?> getClass();

getClass()返回的是一個對象,而getName返回的是一個字符串,雖然他們輸出都是一樣,但是我們必須知道控制臺輸出任何對象,非基礎類型都會轉化為字符串輸出,所以二者的輸出雖然看上去是一樣的,但是有很大的區別

4.clone()

JDK API文檔對說明:

創建並返回此對象的一個副本。“副本”的準確含義可能依賴於對象的類。這樣做的目的是,對於任何對象 x,表達式:

x.clone() != x

為 true,表達式:

x.clone().getClass() == x.getClass()

也為 true,但這些並非必須要滿足的要求。一般情況下:

x.clone().equals(x)

true,但這並非必須要滿足的要求。

我們再來看看源碼:

protected native Object clone() throws CloneNotSupportedException;

我的理解是clone()就是將這個對象重新復制一遍,變成兩個對象,都有著自己的內存空間,而不是只建立一個引用,指向同一片內存空間,註意到x.clone() != x,說明二者是兩個對象,二者的內存空間都是不同的,所以將二者的地址進行比較的時候,肯定是不相等的,註意到x.clone().getClass() == x.getClass(),說明二者運行時的類都是相同的,註意到x.clone().equals(x),雖然不要求一定使這個等式為True,但是一般情況下都是成立的,比如String包裝類,就是說明這二者的字符串是一樣的,但是如果是自定義類等其他類且equals()沒有被重寫的話,那麽這個等式是不成立的,因為equals()沒有被重寫,那麽使用的就是Object下的equals(),比較的是對象的地址,所以等式不成立

那麽為什麽要有clone()呢?

我們現在先看一下clone()與=的區別,舉個栗子:

有一個Car類
Car c1 = new Car();
Car c2 = c1;
這兩句事實上只創建了一個對象。只不過c1和c2指向了同一個對象。
如果上面的兩句改為:
Car c1 = new Car();
Car c2 = c1.clone();
那麽就有了兩個對象,而且這兩個對象的內容是一樣的。(所有的屬性值相同)

假如現在我們有一個類,它有100個屬性,那麽我們如果這樣:a.name=b.name;這樣的話我們需要進行100此操作,這樣也太不藝術了。。。。,所以我們就有了clone()方法,所有操作一次性完成!

那麽我們現在來看一下clone的具體操作:

要知道我們的java語言取消了指針這個東東,導致了很多人都忽略了對象和引用的區別,java不能通過簡單的賦值來解決對象的復制問題

看看下面這個栗子也可以解釋我們為什麽要有clone()

技術分享圖片
 1  A a1=new A();    
 2  A a2=new A();    
 3  a1.name="a1";    
 4  a2=a1;    
 5  a2.name="a2";    
 6  System.out.println("a1.name="+a1.name);    
 7  System.out.println("a2.name="+a2.name);    
 8  //此時輸出的結果是:  
 9  //a1.name=a2    
10  //a2.name=a2    
技術分享圖片

如果我們要用a2保存a1對象的數據,但又不希望a2對象數據被改變時不影響到a1。實現clone()方法是其一種最簡單,也是最高效的手段。下面我們來看看clone的分類

淺層clone():用於類屬性只有Java基本類型和String類型的時候

深層clone:當類屬性有數組和復雜類型的時候

徹底clone:當類屬性有更加復雜的類型的時候,比如Vector對象容器

下面我們用栗子來進行說明:

淺層復制

技術分享圖片
 1 package test;
 2 
 3 public class A implements Cloneable{
 4         public String name;
 5         public Object clone() {
 6             A obj = null;
 7             try {
 8                 obj = (A)super.clone();
 9             } catch (CloneNotSupportedException e) {
10                 // TODO Auto-generated catch block
11                 e.printStackTrace();
12             }
13             return obj;
14         }
15         
16     public static void main(String[] args) {
17         // TODO Auto-generated method stub
18         A a1 = new A();
19         A a2 = new A();
20         a1.name="a1";
21         a2 = (A) a1.clone();
22         a2.name = "a2";
23         System.out.println("a1.name="+a1.name);
24         System.out.println("a2.name="+a2.name);
25     }
26     /*
27      * 結果:
28      * a1.name=a1
29      * a2.name=a2
30      */
31 }
技術分享圖片

這就是所謂的淺層復制,我們可以看到我們的name是字符串型的,如果我們的name是字符串數組類型的,仍然用淺層復制,那麽又會有什麽問題呢?看看下面的栗子:

技術分享圖片
 1 package test;
 2 
 3 public class A implements Cloneable{
 4         public String name[];
 5         public A() {
 6             name = new String[2];
 7         }
 8         public Object clone() {
 9             A obj = null;
10             try {
11                 obj = (A)super.clone();
12             } catch (CloneNotSupportedException e) {
13                 // TODO Auto-generated catch block
14                 e.printStackTrace();
15             }
16             return obj;
17         }
18         
19     public static void main(String[] args) {
20         // TODO Auto-generated method stub
21         A a1 = new A();
22         A a2 = new A();
23         a1.name[0]="a";
24         a1.name[1]="1";
25         a2=(A)a1.clone();
26         a2.name[0]="b";
27         a2.name[1]="1";
28         System.out.println("a1.name="+a1.name);
29         System.out.println("a1.name="+a1.name[0]+a1.name[1]);
30         System.out.println("a2.name="+a2.name);
31         System.out.println("a2.name="+a2.name[0]+a2.name[1]);  
32         /*
33          * 結果:
34          *  a1.name=[Ljava.lang.String;@7852e922
35          *  a1.name=a1
36          *  a2.name=[Ljava.lang.String;@4e25154f
37          *  a2.name=a2
38          */
39         /*分析:
40          * 可以看到我們的a1.name輸出的是name數組的地址
41          * 且輸出的a1.name和a2.name都是b1,說明在進行對象復制的時候對name數組只是復制了它的地址,如果要解決這種情況的話,我們就要采用深層復制
42          */
43     }
44 }
技術分享圖片

深層clone()

技術分享圖片
 1 package test;
 2 
 3 public class A implements Cloneable{
 4         public String name[];
 5         public A() {
 6             name = new String[2];
 7         }
 8         public Object clone() {
 9             A obj = null;
10             try {
11                 obj = (A) super.clone();  
12                 obj.name=(String[])name.clone();//這是實現方式  
13             } catch (CloneNotSupportedException e) {
14                 // TODO Auto-generated catch block
15                 e.printStackTrace();
16             }
17             return obj;
18         }
19         
20     public static void main(String[] args) {
21         // TODO Auto-generated method stub
22         A a1 = new A();
23         A a2 = new A();
24         a1.name[0]="a";
25         a1.name[1]="1";
26         a2=(A)a1.clone();
27         a2.name[0]="b";
28         a2.name[1]="1";
29         System.out.println("a1.name="+a1.name);
30         System.out.println("a1.name="+a1.name[0]+a1.name[1]);
31         System.out.println("a2.name="+a2.name);
32         System.out.println("a2.name="+a2.name[0]+a2.name[1]);  
33         /*
34          * 結果:
35          *  a1.name=[Ljava.lang.String;@7852e922
36          *  a1.name=a1
37          *  a2.name=[Ljava.lang.String;@4e25154f
38          *  a2.name=b1
39          */
40         /*分析:
41          * 可以看到我們輸出的a1.name=a1b1.name=b1,說明我們在復制對象的時候數組name的負責傳遞的不是地址,而是開辟了另外的一片儲存空間,而不是兩個引用指向同一片內存空間
42          * 
43          */
44     }
45 }
技術分享圖片

這就是深層復制,可是如果我們類的屬性有Vector等儲存對象地址的容器的時候,我們就必須進行徹底的clone(),栗子如下:

徹底clone()

技術分享圖片
 1 package test;
 2 
 3 public class B implements Cloneable{
 4     public String x="520";
 5     public Object clone() {
 6         B obj = null;
 7         try {
 8             obj = (B) super.clone();
 9         } catch (CloneNotSupportedException e) {
10             // TODO Auto-generated catch block
11             e.printStackTrace();
12         }  
13         return obj;
14     }
15 }
16 
17 package test;
18 
19 import java.util.Vector;
20 public class A implements Cloneable{
21         public String name[];
22         public Vector<B> clab;
23         public A() {
24             name = new String[2];
25             clab=new Vector<B>();
26         }
27         public Object clone() {
28             A obj = null;
29             try {
30                 obj = (A) super.clone();  
31                 obj.name=(String[])name.clone();//這是實現方式  
32                 obj.clab=new Vector<B>();
33                 for(int i=0;i<clab.size();i++) {//徹底clone
34                     B temp = (B)clab.get(i).clone();//class B 也必須實現clone方法
35                     obj.clab.add(temp);
36                 }
37             } catch (CloneNotSupportedException e) {
38                 // TODO Auto-generated catch block
39                 e.printStackTrace();
40             }
41             return obj;
42         }
43         
44     public static void main(String[] args) {
45         // TODO Auto-generated method stub
46         A a1 = new A();
47         A a2 = new A();
48         B b1 = new B();
49         a1.name[0]="a";
50         a1.name[1]="1";
51         a1.clab.add(b1);
52         
53         a2=(A)a1.clone();
54 
55         System.out.println("a2.name="+a2.name);
56         System.out.println("a2.name="+a2.name[0]+a2.name[1]);
57         System.out.println(a2.clab.get(0).x);
58         
59         /*
60          * 結果:
61          *  a1.name=[Ljava.lang.String;@7852e922
62          *  a1.name=a1
63          *  520
64          */
65         /*分析:
66          * 如果類的屬性裏面有對象容器,那麽必須采用徹底負責
67          */
68     }
69 }
技術分享圖片

5.toString()

先看看文檔對此的說明:

返回該對象的字符串表示。通常,toString 方法會返回一個“以文本方式表示”此對象的字符串。結果應是一個簡明但易於讀懂的信息表達式。建議所有子類都重寫此方法。

總結一下就是返回這個對象的字符串表現形式

下面我們看看源碼:

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

可以看到返回的字符串是由對象的名稱和對象的hashcode組成的

6.finalize()

我們先來看一下文檔對此的說明:

當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。子類重寫 finalize 方法,以配置系統資源或執行其他清除。

finalize 的常規協定是:當 JavaTM 虛擬機已確定尚未終止的任何線程無法再通過任何方法訪問此對象時,將調用此方法,除非由於準備終止的其他某個對象或類的終結操作執行了某個操作。finalize 方法可以采取任何操作,其中包括再次使此對象對其他線程可用;不過,finalize 的主要目的是在不可撤消地丟棄對象之前執行清除操作。例如,表示輸入/輸出連接的對象的 finalize 方法可執行顯式 I/O 事務,以便在永久丟棄對象之前中斷連接。

Object 類的 finalize 方法執行非特殊性操作;它僅執行一些常規返回。Object 的子類可以重寫此定義。

protected void finalize() throws Throwable { }
}

總結一下就是:當某個對象被Java虛擬機回收的時候執行這個方法,如果我們想在這個對象被回收的時候做點什麽,比如再次使此對象對其他線程可用,那麽就需要我們重寫這個方法。

7.notify(),notifyAll(),wait()

這3個方法就是專為線程而生的,要實現線程的安全我們有synchronized,而這3個方法就為線程的同步而準備的

首先我們先來了解一下線程同步和線程互斥

線程同步:就是協調步調,一個任務,多個線程完成,線程的執行有先後順序,這個就叫線程同步

線程互斥:A線程調用a資源的時候B線程就不能調用a資源,得等到A線程調用該資源結束之後,B線程才能調用資源a,這個就叫線程互斥

wait():如果對象調用了該方法,就會使持有該對象的進程把對象的控制器交出去,然後處於等待狀態

notify():如果對象調用了該方法,就會隨機通知某個正在等待該對象控制器的線程可以繼續運行

notifyAll():如果對象調用了該方法,就會通知所有正在等待該對象控制器的線程可以繼續運行

同時wait還可以指定等待的時間長度,如果是默認的話,就一直等待直到被通知

[ 轉載 ] Java基礎10--關於Object類下所有方法的簡單解析