Java的一些父類(一)

   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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章