Java的一些特性會讓初學者感到困惑,但在有經驗的開發者眼中,卻是合情合理的。
例如,新手可能不會理解Object類。本文講分成三個部分講跟Object類及其方法有關的問題。
Object類
Object類存儲在java.lang包中,是所有java類(Object類除外)的父類。當然,數組也繼承了Object類。
然而,接口是不繼承Object類的,原因在這裏指出:Section 9.6.3.4 of the Java Language Specification:“Object類不作爲接口的父類”。
Object類中聲明瞭以下函數
protected Object clone()
boolean equals(Object obj)
protected void finalize()
Class<?> getClass()
int hashCode()
void notify()
void notifyAll()
String toString()
void wait()
void wait(long timeout)
void wait(long timeout, int nanos)
java的任何類都繼承了這些函數,並且可以覆蓋不被final修飾的函數。
例如,沒有final修飾的toString()函數可以被覆蓋,但是finalwait()函數就不行。
聲明要繼承的Object類
在代碼中明確地寫出繼承Object類沒有語法錯誤。
代碼清單1:明確的繼承Object類
import java.lang.Object;
public class Employee extends Object {
private String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static void main(String[] args) {
Employee emp = new Employee("John Doe");
System.out.println(emp.getName());
}
}
你可以試着編譯代碼1(javacEmployee.java),然後運行Employee.class(javaEmployee),可以看到John Doe 成功的輸出了。
因爲編譯器會自動引入java.lang包中的類型,即 import java.lang.Object; 沒必要聲明出來。Java也沒有強制聲明“繼承Object類”。如果這樣的話,就不能繼承除Object類之外別的類了,因爲java不支持多繼承。然而,即使不聲明出來,也會默認繼承了Object類,
參考代碼清單2:默認繼承Object類
public class Employee
{
private String name;
public Employee(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public static void main(String[] args)
{
Employee emp = new Employee("John Doe");
System.out.println(emp.getName());
}
}
就像代碼清單1一樣,這裏的Employee類繼承了Object,所以可以使用它的函數。
克隆Object類
1.clone()函數可以產生一個相同的類並且返回給調用者。
2.clone()函數的工作機制:Object將clone()作爲一個本地方法來實現,這意味着它的代碼存放在本地的庫中。當代碼執行的時候,將會檢查調用對象的類(或者父類)是否實現了java.lang.Cloneable接口(Object類不實現Cloneable)。如果沒有實現這個接口,clone()將會拋出一個檢查異常()——java.lang.CloneNotSupportedException,如果實現了這個接口,clone()會創建一個新的對象,並將原來對象的內容複製到新對象,最後返回這個新對象的引用。
3.clone()函數克隆對象用想要克隆的對象來調用clone(),將返回的對象從Object類轉換到克隆的對象所屬的類,賦給對象的引用。
代碼清單3:克隆一個對象
public class CloneDemo implements Cloneable{
intx;
public static void main(String[] args) throws CloneNotSupportedException{
CloneDemo cd = new CloneDemo();
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(javacCloneDemo.java)然後運行(java CloneDemo)。你可以看到以下運行結果:
cd.x = 5
cd2.x = 5
4.覆蓋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:從別的類中克隆對象
class Data implements Cloneable {
intx;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException{
Data data = new Data();
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(javacCloneDemo.java)並運行(java CloneDemo),你將看到以下運行結果:
data.x = 5
data2.x = 5
淺克隆
A:淺克隆(也叫做淺拷貝)僅僅複製了這個對象本身的成員變量,該對象如果引用了其他對象的話,也不對其複製。代碼清單3和代碼清單4演示了淺克隆。新的對象中的數據包含在了這個對象本身中,不涉及對別的對象的引用。
如果一個對象中的所有成員變量都是原始類型,並且其引用了的對象都是不可改變的(大多情況下都是)時,使用淺克隆效果很好!但是,如果其引用了可變的對象,那麼這些變化將會影響到該對象和它克隆出的所有對象!代碼清單5給出一個示例。
代碼清單5:演示淺克隆在複製引用了可變對象的對象時存在的問題
class Employee implements Cloneable {
private String name;
private int age;
private Address address;
Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
Address getAddress() {
return address;
}
String getName() {
return name;
}
intgetAge() {
return age;
}
}
class Address {
private String city;
Address(String city) {
this.city = city;
}
String getCity() {
return city;
}
void setCity(String city) {
this.city = city;
}
}
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException{
Employee e = new Employee("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對象,所以兩者都會提現出這個變化。
編譯 (javacCloneDemo.java) 並運行 (java CloneDemo)代碼清單5,你將會看到如下輸出結果:
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Chicago
深克隆
深克隆(也叫做深複製)會複製這個對象和它所引用的對象的成員變量,如果該對象引用了其他對象,深克隆也會對其複製。例如,代碼清單6在代碼清單5上稍作修改演示深克隆。同時,這段代碼也演示了協變返回類型和一種更爲靈活的克隆方式。
代碼清單6:深克隆成員變量address
class Employee implements Cloneable
{
private String name;
private int age;
private Address address;
Employee(String name, int age, Addressaddress)
{
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Employee clone() throwsCloneNotSupportedException
{
Employee e = (Employee) super.clone();
e.address = address.clone();
return e;
}
Address getAddress()
{
return address;
}
String getName()
{
return name;
}
intgetAge()
{
return age;
}
}
class Address
{
private String city;
Address(String city)
{
this.city = city;
}
@Override
public Address clone()
{
return new Address(new String(city));
}
String getCity()
{
return city;
}
voidsetCity(String city)
{
this.city = city;
}
}
public class CloneDemo
{
public static void main(String[] args) throwsCloneNotSupportedException
{
Employee e = new Employee("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並且運行這個程序,你將會看到如下輸出結果(javaCloneDemo):
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Denver
克隆一個數組
A:對數組類型進行淺克隆可以利用clone()方法。對數組使用clone()時,不必將clone()的返回值類型轉換爲數組類型,代碼清單7示範了數組克隆。
代碼清單7:對兩個數組進行淺克隆
class City {
private String name;
City(String name) {
this.name = name;
}
String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
}
public class CloneDemo {
public static void main(String[] args) {
double[] temps = { 98.6, 32.0, 100.0, 212.0, 53.5 };
for (double temp : temps)
System.out.printf("%.1f ", temp);
System.out.println();
double[] temps2 = temps.clone();
for (double temp : temps2)
System.out.printf("%.1f ", temp);
System.out.println();
System.out.println();
City[] cities = { new City("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()最後改變了原來的數組中第一個城市的名字,輸出第二個數組中所有城市的名字。我們馬上就可以看到,第二個數組中的名字也改變了。
編譯 (javacCloneDemo.java)並運行 (java CloneDemo)代碼清單7,你將會看到如下輸出結果:
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