Java的接口是支持多繼承的。Java是不支持實現上的多繼承。
2. 避免interfaces 中的函數發生衝突。
如果兩個接口具有相同的方法名字,但是其返回類型不一樣,那麼其實現類不能夠同時實現這兩個接口,編譯會報錯。
如果兩個接口的具有相同的方法名字,並且返回類型也一致,那麼其實現類可以同時實現這兩個接口,編譯不會報錯。
如果兩個接口聲明的變量是一模一樣的話,那麼在實現類中調用變量的時候,需要指定是哪個接口的變量,或者編譯不會通過。
3. 如需提供部分實現(partial implementation),請使用abstract classes(抽象類)。
所謂abstract class 是一種本身不能被具現化(instantiated)的class,但允許爲其內聲明的函數提供實現代碼。不同與interface(接口),接口是不能夠有實現。
4. 區分interface、abstract class和concrete class。
|
Interface |
Abstract class |
Concrete class |
描述 |
一種契約(contract)表示法,不 帶實現 |
一種契約(contract)表示法,帶有部分實現 |
一種具象(具體)實現,經常是某個interface 或者某個abstract class 的實現 |
使用時機 |
當你想要支持單一或多個interface 繼承,或爲了確定某一marker interface(標識接口) |
當你打算提供帶有部分實現的class |
當你打算提供一份完 整的具象實現 |
內容限制 |
Public 函數以及public static final 常量 |
無任何限制 |
無任何限制 |
實現 |
不允許實現 |
允許部分實現 |
全部實現 |
interface、abstract classes 和concrete classes 的總結
|
支持多重繼承 |
支持抽象函數 |
允許實現 |
允許創建實體 |
允許部分實現 |
|
Interface |
Y |
Y |
N |
N |
N |
|
abstract classes |
N |
Y |
Y |
N |
Y |
|
concrete classes |
N |
N |
Y |
Y |
N |
|
interface 的所有函數都暗自爲abstract。
5. 審慎地定義和實現immutable classes (不可變類)。
不可變對象是一種一點構建好就不再變化的object,在其生存期間不可被改變內容,不可變類通常用來表示字符串、顏色和數字。由於不可變對象永遠不會改變本身數據,所以沒有必要對他們同步控制。
Class的定義和實現多方面合作纔可能造就不變性,以下幾個步驟是必須的:
a. 將class中的所有數據聲明爲private;(如果聲明爲其他修飾符的話,實例類則可以修改數據信息)
b. 只提供取值方法(getter),不允許存在設置方法(setter);(如果提供了setter方法,那麼其數據信息肯定會被修改掉)
c. 將class聲明爲final(即使提供了getter方法,但是在子類中也覆蓋父類方法的話,仍然有可能是修改數據,因此需要將類聲明爲不能夠被繼承);
d. 從獲取器返回reference to mutable object之前,先克隆(cloning)那些mutable objects;
e. 將[傳遞給構造函數之reference to mutable object]先克隆(clone);
f. 在構造函數中設定class 內含的所有數據。(由於數據信息不能夠被修改,所以其賦值只能夠是在構造函數)
6. 欲傳遞或接收mutable objects(可變對象)之object references時,請實施clone()。
實現immutable class(不可變類)時,必須對傳入或傳出之mutable objects(可變對象)進行克隆(clone)動作。如果一個object及其指涉(引用)所有objects都不會被改變,該object便是爲恆常不變的(immutable)。如果不實施克隆動作,你的對象不變性(immutability)就得不到保證。這是因爲他處可能保留了這個immutable object 內的某個object 的reference,並對其進行了修改,從而衝破了不變性的[界限]。
就是說,如果一個對象A中還引用了其他對象的引用B,那麼引用B中的數據是可以被修改掉。如下代碼:
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public final class DiskInfo {
private String disk;
private User user;
public DiskInfo(String disk, User user) {
this.disk = disk;
this.user = user;
}
public User getUser() {
return user;
}
public String disk() {
return disk;
}
public static void main(String[] args) {
User u = new User();
u.setAge(1);
u.setName("test1");
DiskInfo d = new DiskInfo("test", u);
User u1 = d.getUser();
System.out.println("-----" + u1.getName());
u1.setName("test2");
User u2 = d.getUser();
System.out.println("-----" + u2.getName());
}
}
其輸入結果:
-----test1
-----test2
可以看到上述的結論是正確的。因此爲了保持數據的不變性,需要將mutable對象進行克隆。
克隆:Shallow Cloning(淺層克隆)。此技術對對象進行逐位拷貝(bitwise copy)。如果cloned object 包含了一些object references,那麼新的object 將內含與[cloned object 所含之object reference]完全一致的副本。所以new object 和cloned object 依然共享數據。使用了克隆的代碼如下:
package com.study6;
public class User implements Cloneable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
}
package com.study6;
public final class DiskInfo {
private String disk;
private User user;
public DiskInfo(String disk, User user) {
this.disk = disk;
this.user = (User)user.clone();
}
public User getUser() {
return (User)user.clone();
}
public String disk() {
return disk;
}
public static void main(String[] args) {
User u = new User();
u.setAge(1);
u.setName("test1");
DiskInfo d = new DiskInfo("test", u);
User u1 = d.getUser();
System.out.println("-----" + u1.getName());
u1.setName("test2");
User u2 = d.getUser();
System.out.println("-----" + u2.getName());
}
}
輸出結果:
-----test1
-----test1
Deep Cloning(深層克隆):缺省情況下,Vector clone()執行的是淺層克隆(shallow clone)。Vector的值域driveShare其實是個object reference,因此當代碼對Vector進行克隆時,雖然建立了一個新副本,其內容(references to User object)卻沒有被克隆。因此需要深層克隆。
在實現—個immutable class(不可變類)時,請遵循下列規則:
a.聲明這個class 爲final。
b.聲明所有數據爲private。
c.只提供取值函數(getter),不提供設值函數(setter)。
d.在構造函數中設置所有instance 數據。
e. 如果函數返回reference to mutable objects,請克隆那些mutable objects。
f.如果函數接受reference to mutable objects,請克隆那些mutable objects。
g.如果缺省之淺層克隆(shallow clone)不能符合immutable object 的正常行爲請實現出深層克隆(deep clone)。
7. 使用繼承(inheritance)或委託(delegation)來定義immutable classes(不可變類)。
另外三個你可以用來定義immutable classes 的技術:
a.immutable interface(不可變接口)。
b.公共接口或基類(common interface or base class)。
c.immutable delegation class (不可變的委託類)。
技術 |
優點 |
缺點 |
Immutable inference 不可變(恆常)接口 |
簡單易行。無需付出性能上的代價 |
有破綻,可被破壞。(轉型會破壞不變性) |
Common interface or base calss 公共接口或基類 |
沒有破綻。清晰地將 mutable object 和immutable objects 分離 |
需實現更多的classes,完成更深的classes 繼承系(一個接口必須有多個實現類) |
Immutable delegation class不可變(恆常)委託類 |
沒有破綻。當你無法修改既 有的mutable object 源碼 時,此法可用 |
需付出性能上的代價(不可變類中含有一個可變類的引用,造成性能下降) |
8.實現clone()時記得調用super.clone()。
clone ()中首先必須調用super.clone(),可確保java.long.Object 中的clone會被調用,因而使得克隆件得以被正確建構出來。java.1ang.Object 的clone()函數會創建一個正確型別的新對象,並執行淺層克隆(shallow clone,即從[被克隆物]複製所有位域(fields)到[新對象]身上)。即使需要深層克隆(deep clone),仍然齋要調用java.1ang.Object 的clone()函數,才能創建對象的正確型別。
任何classes 如果支持克隆操作,就必須實現Cloneable interface——這是一個標識接口(marker interface),也就是說它並沒有實現任何函數,任何classes 如果實現Cloneable,就是宣稱它們支持克隆操作。
9. 別隻依賴finalize()清理non—memory(內存以外)的資源
在垃圾問收器(garbage collector)回收某個class object 之前,JVM 會調用這個class 的finalize()。這個函數有時被宣揚爲[在回收某個對象的內存之前確保釋放non-memory 資源]的有效方法。由於垃圾回收器僅僅釋放對象內存,finalize()因此提供了一種釋放其他資源的辦法。
對象的finalize()函數只有在垃圾回收器釋放對象佔用的空間之前纔會被調用。
當垃圾回收器執行時,可能並非所有符合回收條件的對象都被回收(實際情況取決於垃圾回收機制所採用之算法),此外亦無法保證finalize()在可預期的時間上執行。這是由於[終結(finalization)動作[與垃圾回收]之間的異步(asynchronous)本性造成的。由此推斷,finalize()未必在程序結束前被執行。這意味即使你正確撰寫了finalize()來釋放non-memory 資源,你的程序結束前仍有可能耗盡這些資源。
避免這個問題的可能辦法之一是使用System.runFinalization()。這個函數要求JVM 在垃圾回收器下次運行之前,對所有標記爲[可終結(finalizable)]對象調用其finalize()。是否能夠做到這一點,取決於垃圾回收器和這個函數所採用的算法。但不能保證調用該函數就一定會執行起finalize()。
早先的另一個可用函數是System.runFinalization(),現已作廢。這個函數僅僅保證所有對象的finalize()將在JVM 退出之前被執行。這意味等到finalize()執行時,你可能已經耗盡了資源。通常你會希望finalize()在你程序運行期間被執行,而不是等到最後JVM退出時刻。這個函數被認爲是不安全的,因此在Java2 中被廢除了。
因此不能把希望單單寄託於[finalize()被調用]這件事情上,需要自己編寫清理no-memory資源的函數,並且結合finalize方法一起使用。可以自己編寫一個清理函數,並且該函數是public的,同時要被finalize函數所調用(確定JVM調用finalize時可以調用到清理函數)。將清理函數聲明爲public,是爲了可以再任何時刻都可以清理資源。將清理函數被聲明爲synchronized,這可以保證多個線程(threads)不會針對相同的對象同時進入這個函數。
所有finalize()都應當調用super.finalize()以確保任superclass 的finalize()被調用。由於無法保證finalize()是否被調用、何時被調用,所以你應當提供一個Public 專員函數,用以執行non-momory 資源清理工作;該函數應當由class 的finalize()調用之。
10. 在構造函數內調用non—final函數時要當心
當新建一個對象的時候,構造方法能夠被調用。
package com.study6;
public class NoFinalTest {
/**
* @param args
*/
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.getNum());
}
}
class Base {
private int value;
public Base() {
System.out.println("base");
value = lookUp();
}
public int lookUp() {
System.out.println("base lookUp");
return 5;
}
public int getNum() {
return value;
}
}
class Sub extends Base {
private int num = 10;
public int lookUp() {
System.out.println("Sub lookUp " + num);
return num;
}
}輸出結果爲:
base
Sub lookUp 0
0
.這不是我們想要的結果,不過從上可以看出來num並還沒有賦值。
分析:該函數返回instance變量num,它在instance 變量初始化時被賦值爲10.事實真象是,當Sub的1ookup()開始執行時,其instance變量初始化工作還未來得及進行呢。
當構造函數調用non-final 函數時,就有可能發生這種錯誤。如果該函數被一個derived class 覆寫,而是該函數返回一個[instance 變量初始化期間]被初始化的值,就會產生問題。