Java 問答:終極父類(第一部分)

Java的一些特性會讓初學者感到困惑,但在有經驗的開發者眼中,倒是合情合理的。例如,新手可能不會理解Object類。這篇文章分紅三個部分講跟Object類及其方法有關的問題。 html

上帝類

問:什麼是Object類? java

答:Object類存儲在java.lang包中,是全部java類(Object類除外)的終極父類。固然,數組也繼承了Object類。然而,接口是不繼承Object類的,緣由在這裏指出:Section 9.6.3.4 of the Java Language Specification:「Object類不做爲接口的父類」。
Object類中聲明瞭如下函數,我會在下文中做詳細說明。 數組

  • protected Object clone() oracle

  • boolean equals(Object obj) ide

  • protected void finalize() 函數

  • Class<?> getClass() this

  • int hashCode() spa

  • void notify() htm

  • void notifyAll() 對象

  • String toString()

  • void wait()

  • void wait(long timeout)

  • void wait(long timeout, int nanos)

java的任何類都繼承了這些函數,而且能夠覆蓋不被final修飾的函數。例如,沒有final修飾的toString()函數能夠被覆蓋,可是final wait()函數就不行。

問:能夠聲明要「繼承Object類」嗎?

答:能夠。在代碼中明確地寫出繼承Object類沒有語法錯誤。參考代碼清單1。

代碼清單1:明確的繼承Object類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
importjava.lang.Object;
publicclassEmployeeextendsObject {
   privateString name;
   publicEmployee(String name) {
       this.name = name;
   }
   publicString getName() {
       returnname;
   }
   publicstaticvoidmain(String[] args) {
       Employee emp =newEmployee("John Doe");
       System.out.println(emp.getName());
   }
}

你能夠試着編譯代碼1(javac Employee.java),而後運行Employee.class(java Employee),能夠看到John Doe 成功的輸出了。

由於編譯器會自動引入java.lang包中的類型,即import java.lang.Object; 不必聲明出來。Java也沒有強制聲明「繼承Object類」。若是這樣的話,就不能繼承除Object類以外別的類了,由於java不支持多繼承。然而,即便不聲明出來,也會默認繼承了Object類,參考代碼清單2。

代碼清單2:默認繼承Object類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
publicclassEmployee
{
 privateString name;
 publicEmployee(String name)
 {
     this.name = name;
 }
 publicString getName()
 {
     returnname;
 }
 publicstaticvoidmain(String[] args)
 {
     Employee emp =newEmployee("John Doe");
     System.out.println(emp.getName());
 }
}

就像代碼清單1同樣,這裏的Employee類繼承了Object,因此可使用它的函數。

克隆Object類

問:clone()函數是用來作什麼的?

答:clone()能夠產生一個相同的類而且返回給調用者。

問:clone()是如何工做的?

答:Object將clone()做爲一個本地方法來實現,這意味着它的代碼存放在本地的庫中。當代碼執行的時候,將會檢查調用對象的類(或者父類)是否實現了java.lang.Cloneable接口(Object類不實現Cloneable)。若是沒有實現這個接口,clone()將會拋出一個檢查異常()——java.lang.CloneNotSupportedException,若是實現了這個接口,clone()會建立一個新的對象,並將原來對象的內容複製到新對象,最後返回這個新對象的引用。

問:怎樣調用clone()來克隆一個對象?

答:用想要克隆的對象來調用clone(),將返回的對象從Object類轉換到克隆的對象所屬的類,賦給對象的引用。這裏用代碼清單3做一個示例。

代碼清單3:克隆一個對象

1
2
3
4
5
6
7
8
9
10
11
publicclassCloneDemoimplementsCloneable {
   intx;
   publicstaticvoidmain(String[] args)throwsCloneNotSupportedException {
       CloneDemo cd =newCloneDemo();
       cd.x =5;
       System.out.printf("cd.x = %d%n", cd.x);
       CloneDemo cd2 = (CloneDemo) cd.clone();
       System.out.printf("cd2.x = %d%n", cd2.x);
   }
}

代碼清單3聲明瞭一個繼承Cloneable接口的CloneDemo類。這個接口必須實現,不然,調用Object的clone()時將會致使拋出異常CloneNotSupportedException。

CloneDemo聲明瞭一個int型變量x和主函數main()來演示這個類。其中,main()聲明可能會向外拋出CloneNotSupportedException異常。

Main()先實例化CloneDemo並將x的值初始化爲5。而後輸出x的值,緊接着調用clone(),將克隆的對象傳回CloneDemo。最後,輸出了克隆的x的值。

編譯代碼清單3(javac CloneDemo.java)而後運行(java CloneDemo)。你能夠看到如下運行結果:

1
2
cd.x = 5
cd2.x = 5

問:什麼狀況下須要覆蓋clone()方法呢?

答:上面的例子中,調用clone()的代碼是位於被克隆的類(即CloneDemo類)裏面的,因此就不須要覆蓋clone()了。可是,若是調用別的類中的clone(),就須要覆蓋clone()了。不然,將會看到「clone在Object中是被保護的」提示,由於clone()在Object中的權限是protected。(譯者注:protected權限的成員在不一樣的包中,只有子類對象能夠訪問。代碼清單3的CloneDemo類和代碼清單4的Data類是Object類的子類,因此能夠調用clone(),可是代碼清單4中的CloneDemo類就不能直接調用Data父類的clone())。代碼清單4在代碼清單3上稍做修改來演示覆蓋clone()。

代碼清單4:從別的類中克隆對象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classDataimplementsCloneable {
   intx;
   @Override
   publicObject clone()throwsCloneNotSupportedException {
       returnsuper.clone();
   }
}
publicclassCloneDemo {
   publicstaticvoidmain(String[] args)throwsCloneNotSupportedException {
       Data data =newData();
       data.x =5;
       System.out.printf("data.x = %d%n", data.x);
       Data data2 = (Data) data.clone();
       System.out.printf("data2.x = %d%n", data2.x);
   }
}

代碼清單4聲明瞭一個待克隆的Data類。這個類實現了Cloneable接口來防止調用clone()的時候拋出異常CloneNotSupportedException,聲明瞭int型變量x,覆蓋了clone()方法。這個方法經過執行super.clone()來調用父類的clone()(這個例子中是Object的)。經過覆蓋來避免拋出CloneNotSupportedException異常。

代碼清單4也聲明瞭一個CloneDemo類來實例化Data,並將其初始化,輸出示例的值。而後克隆Data的對象,一樣將其值輸出。

編譯代碼清單4(javac CloneDemo.java)並運行(java CloneDemo),你將看到如下運行結果:

1
2
data.x = 5
data2.x = 5

問:什麼是淺克隆?

A:淺克隆(也叫作淺拷貝)僅僅複製了這個對象自己的成員變量,該對象若是引用了其餘對象的話,也不對其複製。代碼清單3和代碼清單4演示了淺克隆。新的對象中的數據包含在了這個對象自己中,不涉及對別的對象的引用。

若是一個對象中的全部成員變量都是原始類型,而且其引用了的對象都是不可改變的(大多狀況下都是)時,使用淺克隆效果很好!可是,若是其引用了可變的對象,那麼這些變化將會影響到該對象和它克隆出的全部對象!代碼清單5給出一個示例。

代碼清單5:演示淺克隆在複製引用了可變對象的對象時存在的問題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
classEmployeeimplementsCloneable {
   privateString name;
   privateintage;
   privateAddress address;
   Employee(String name,intage, Address address) {
       this.name = name;
       this.age = age;
       this.address = address;
   }
   @Override
   publicObject clone()throwsCloneNotSupportedException {
       returnsuper.clone();
   }
   Address getAddress() {
       returnaddress;
   }
   String getName() {
       returnname;
   }
   intgetAge() {
       returnage;
   }
}
classAddress {
   privateString city;
   Address(String city) {
       this.city = city;
   }
   String getCity() {
       returncity;
   }
   voidsetCity(String city) {
       this.city = city;
   }
}
publicclassCloneDemo {
   publicstaticvoidmain(String[] args)throwsCloneNotSupportedException {
       Employee e =newEmployee("John Doe",49,newAddress("Denver"));
       System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                         e.getAddress().getCity());
       Employee e2 = (Employee) e.clone();
       System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                         e2.getAddress().getCity());
       e.getAddress().setCity("Chicago");
       System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                         e.getAddress().getCity());
       System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                         e2.getAddress().getCity());
   }
}

代碼清單5給出了Employee、Address和CloneDemo類。Employee聲明瞭name、age、address成員變量,是能夠被克隆的類;Address聲明瞭一個城市的地址而且其值是可變的。CloneDemo類驅動這個程序。

CloneDemo的主函數main()建立了一個Employee對象而且對其進行克隆,而後,改變了原來的Employee對象中address值城市的名字。由於原來的Employee對象和其克隆出來的對象引用了相同的Address對象,因此二者都會提現出這個變化。

編譯 (javac CloneDemo.java) 並運行 (java CloneDemo)代碼清單5,你將會看到以下輸出結果:

1
2
3
4
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Chicago

問:什麼是深克隆?

答:深克隆(也叫作深複製)會複製這個對象和它所引用的對象的成員變量,若是該對象引用了其餘對象,深克隆也會對其複製。例如,代碼清單6在代碼清單5上稍做修改演示深克隆。同時,這段代碼也演示了協變返回類型和一種更爲靈活的克隆方式。

代碼清單6:深克隆成員變量address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
classEmployeeimplementsCloneable
{
 privateStringname;
 privateintage;
 privateAddress address;
 Employee(Stringname,intage, Address address)
 {
     this.name = name;
     this.age = age;
     this.address = address;
 }
 @Override
 publicEmployee clone() throws CloneNotSupportedException
 {
     Employee e = (Employee)super.clone();
     e.address = address.clone();
     returne;
 }
 Address getAddress()
 {
     returnaddress;
 }
 StringgetName()
 {
     returnname;
 }
 intgetAge()
 {
     returnage;
 }
}
classAddress
{
 privateStringcity;
 Address(Stringcity)
 {
     this.city = city;
 }
 @Override
 publicAddress clone()
 {
     returnnewAddress(newString(city));
 }
 StringgetCity()
 {
     returncity;
 }
 voidsetCity(Stringcity)
 {
     this.city = city;
 }
}
publicclassCloneDemo
{
 publicstaticvoidmain(String[] args) throws CloneNotSupportedException
 {
     Employee e =newEmployee("John Doe",49,newAddress("Denver"));
     System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                       e.getAddress().getCity());
     Employee e2 = (Employee) e.clone();
     System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                       e2.getAddress().getCity());
     e.getAddress().setCity("Chicago");
     System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                       e.getAddress().getCity());
     System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                       e2.getAddress().getCity());
 }
}

Java支持協變返回類型,代碼清單6利用這個特性,在Employee類中覆蓋父類clone()方法時,將返回類型從Object類的對象改成Employee類型。這樣作的好處就是,Employee類以外的代碼能夠不用將這個類轉換爲Employee類型就能夠對其進行復制。

Employee類的clone()方法首先調用super().clone(),對name,age,address這些成員變量進行淺克隆。而後,調用成員變量Address對象的clone()來對其引用Address對象進行克隆。

從Address類中的clone()函數能夠看出,這個clone()和咱們以前寫的clone()有些不一樣:

  • Address類沒有實現Cloneable接口。由於只有在Object類中的clone()被調用時才須要實現,而Address是不會調用clone()的,因此沒有實現Cloneable()的必要。

  • 這個clone()函數沒有聲明拋出CloneNotSupportedException。這個檢查異常只可能在調用Object類clone()的時候拋出。clone()是不會被調用的,所以這個異常也就沒有被處理或者傳回調用處的必要了。

  • Object類的clone()沒有被調用(這裏沒有調用super.clone())。由於這不是對Address的對象進行淺克隆——只是一個成員變量複製而已。

爲了克隆Address的對象,須要建立一個新的Address對象並對其成員進行初始化操做。最後將新建立的Address對象返回。

編譯(javac CloneDemo.java)代碼清單6而且運行這個程序,你將會看到以下輸出結果(java CloneDemo):

1
2
3
4
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Denver

Q:如何克隆一個數組?

A:對數組類型進行淺克隆能夠利用clone()方法。對數組使用clone()時,沒必要將clone()的返回值類型轉換爲數組類型,代碼清單7示範了數組克隆。

代碼清單7:對兩個數組進行淺克隆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
classCity {
   privateString name;
   City(String name) {
       this.name = name;
   }
   String getName() {
       returnname;
   }
   voidsetName(String name) {
       this.name = name;
   }
}
publicclassCloneDemo {
   publicstaticvoidmain(String[] args) {
       double[] temps = {98.6,32.0,100.0,212.0,53.5};
       for(doubletemp : temps)
           System.out.printf("%.1f ", temp);
       System.out.println();
       double[] temps2 = temps.clone();
       for(doubletemp : temps2)
           System.out.printf("%.1f ", temp);
       System.out.println();
       System.out.println();
       City[] cities = {newCity("Denver"),newCity("Chicago") };
       for(City city : cities)
           System.out.printf("%s ", city.getName());
       System.out.println();
       City[] cities2 = cities.clone();
       for(City city : cities2)
           System.out.printf("%s ", city.getName());
       System.out.println();
       cities[0].setName("Dallas");
       for(City city : cities2)
           System.out.printf("%s ", city.getName());
       System.out.println();
   }
}

代碼清單7聲明瞭一個City類存儲名字,還有一些有關城市的數據(好比人口)。CloneDemo類提供了主函數main()來演示數組克隆。

main()函數首先聲明瞭一個雙精度浮點型數組來表示溫度。在輸出數組的值以後,克隆這個數組——注意沒有運算符。以後,輸出克隆的徹底相同的數據。

緊接着,main()聲明瞭一個City對象的數組,輸出城市的名字,克隆這個數組,輸出克隆的這個數組中城市的名字。爲了證實淺克隆完成(好比,這兩個數組引用了相同的City對象),main()最後改變了原來的數組中第一個城市的名字,輸出第二個數組中全部城市的名字。咱們立刻就能夠看到,第二個數組中的名字也改變了。

編譯 (javac CloneDemo.java)並運行 (java CloneDemo)代碼清單7,你將會看到以下輸出結果:

1
2
3
4
5
6
98.6 32.0 100.0 212.0 53.5
98.6 32.0 100.0 212.0 53.5
Denver Chicago
Denver Chicago
Dallas Chicago
相關文章
相關標籤/搜索