第六章 重復運用classes
在面向過程的語言中重復運用代碼只是簡單的復制代碼,以達到重復運用的目的,而在面向對象的Java程序中,代碼的重用主要體現在2點
1、在新的class中使用既有的class,這中方法稱之為"組合"。但是這種重用方式只是很單純的重復運用以有的代碼 功能,而非重復運用其形式。
2、讓新的class成為既有class的一類,並且根據需要加入新的功能,而無須更動原有class,這種方法稱之為"繼承"。
組合語法
其實組合我們在以前的例子中已經大量的用到了,我們只要將對象句柄放置在class中就是組合!
class compostion
{
private String s;
compostion()
{
System.out.println("compostion()");
s=new String("hello");
}
public String toString()
{
return s;
}
}
public class test
{
compostion c; //對象句柄
int i;
public void show()
{
System.out.println("int = "+i);
System.out.println("compostion = "+c);
}
public static void main(String args[])
{
test t = new test();
t.show();
}
}
其中每個非基本數據類型的對象都有一個toString()方法,該函數用於將compostion轉換為一個string,和其他string相加class中基本數據類型會被初始化為默認值,而對象句柄會被初始化為null。如果你要使用該句柄,切記要初始化,否則會抱空指針錯誤!
繼承
繼承是java語言中極其重要的一部分,使用關鍵字extends來實現,這樣變自動的讓子類獲得了父類中所有的成員數據和函數。而Java中所有的類甚至包括你自己已經定義的或者將要定義的類都是繼承自object類的,在編譯器內部進行的隱式繼承
class base
{
int i=10;
public void show()
{
System.out.println("base method");
}
public static void main(String args[]) // Java允許在同一個文件中的class擁有各自的main()
{
new base().show();
}
}
class derived extends base //繼承
{
public void show() //覆蓋了base的函數
{
System.out.println("derived method");
super.show(); //調用base的函數
}
public void newMethod() //子類中新加入的函數
{
System.out.println(i); //打印base中的數據
}
public static void main(String args[])
{
derived d = new derived();
d.show();
d.newMethod();
}
}
base的初始化
當子類被初始化的時候系統會先將被繼承的父類初始化,Java編譯器會在調用子類構造函數之前調用父類的構造函數
class base
{
base()
{
System.out.println("base method");
}
}
class derived extends base
{
derived()
{
//super(); 系統會自動加入對父類的調用
System.out.println("derived method");
}
public static void main(String args[])
{
derived d = new derived();
}
}
假如你的父類是帶有引數的class,那麼編譯器是不會自動調用構造函數的,你必須使用super來調用,否則系統會抱錯
class base
{
base(String s)
{
System.out.println(s);
}
}
class derived extends base
{
derived()
{
super("base method"); //必須是構造函數的第一行語句
System.out.println("derived method");
}
public static void main(String args[])
{
derived d = new derived();
}
}
兼容組合和繼承
有的時候,我們在編寫class的時候不但用到組合,還用到繼承。
組合和繼承之間的選擇
如果你只是希望在新class中使用到既有class的功能,並且隱藏起實現細目,那麼最好使用組合。
如果你希望為既有的class開發一種特殊版本,那麼繼承再好不過了。
如果你的既有類和新類是一種is a的(是一個)關系,那麼使用繼承,如果是一種has a(有一個)關系,那麼使用組合
protected
我們再來復習一下protexted的意義:繼承此class的子類,可以訪問該類的成員,並且對於一個包內的其他類是frIEndly的
漸進式開發
繼承的優點之一就是支持漸進式開發,在這種開發模式下,你可以在程序中加如新的程序代碼,但是卻不影響父類的代碼
向上轉型
由於繼承的關系,在子類中可以使用父類的所有函數,並且任何發送給父類的消息,也可以發送給子類
class base
{
protected void show()
{
System.out.println("base method");
}
protected void getSomeone( base b)
{
b.show();
}
}
class derived extends base
{
public static void main(String args[])
{
derived d = new derived();
d.getSomeone(d);
}
}
注意在getsomeone的函數定義中,我們定義的是他只能接受一個base類的句柄,然而他居然接受了子類的句柄。因為子類雖然和父類不太相同,但是他畢竟是父類的一種因此適用與父類的函數當然也適用與子類咯,我們把這種把子類轉型為父類的做法叫做向上轉型upcasting
class base
{
public void methodOne()
{
System.out.println("base.methodOne");
}
public void methodTwo()
{
System.out.println("base.methodTwo");
}
}
class derived extends base
{
public void methodOne()
{
System.out.println("derived.methodOne");
}
public void methodTwo()
{
System.out.println("derived.methodTwo");
}
public void methodNew()
{
System.out.println("derived.methodNew");
}
public static void main(String[] args)
{
base d = new derived();
d.methodOne(); //雖然已經向上轉型成為了base,但是調用的函數還是基類的函數,顯示derived.methodOne
d.methodTwo(); //同上
//d.methodNew(); 因為已經向上轉型,所以丟失了子類特有的函數
}
}
為什麼需要向上轉型?
子類是父類的一個超集,因此子類中至少包含父類中的函數,並且可能會更多。然而向上轉型會使子類遺失和父類不同的方法,向上轉型一定是安全的,因為這是從特殊類型改變成通用類型。
組合vs繼承
當你需要向上轉型的時候是使用繼承的最佳時間
關鍵字final
什麼是final?就是"最終"的意思,也就是不可改變的意思,我們在這裡討論3中final:data、method、class
final data
final的數據是固定不變的,不變的數據稱之為常量,他是很有用的,因為它
1、可以是永不改變的編譯期常量。編譯期常量可以在編譯期執行某些計算,減少執行期的負擔,此類常量必須是基本數據類型,使用final修飾,必須給定初值。
2、可以在執行期被初始化,而你卻不想再改變他。
如果某個數據既有static還有final,那麼他就會擁有一塊無法改變的儲存空間。當把final用於對象時,final讓句柄保持不變不能再重新指向其他對象,然而對象本身卻是可以改變的,這點和final的基本數據類型不能改變其值的特點有所不同。
class Value
{
int i = 1;
}
class finalData
{
//編譯期常量
final int i = 10;
static final int II = 20;
//典型的常量
public static final int III= 30;
//執行期常量
final int iiii = (int)(Math.random()*20);
static final int iiiii = (int)(Math.random()*20);
Value v = new Value();
final Value vv =new Value();
static final Value vvv =new Value();
//數組
final int[] a = { 1,2,3,4,5,6};
public void show(String id)
{
System.out.println(id+" : "+"iiii ="+iiii+" , iiiii = "+ iiiii);
}
public static void main(String[] args)
{
finalData fd = new finalData();
//fd.i++; 不能改變值
fd.vv.i++; //final對象可以改變其對象值
fd.v=new Value(); //非final
for(int i = 0;i
fd.a[i]++; //final對象可以改變其值
//fd.vv = new Value(); //final對象的句柄不能改變其引用的對象
//fd.vvv =new Value(); //final對象的句柄不能改變其引用的對象
//fd.a = new int[3]; //final對象的句柄不能改變其引用的對象
fd.show("fd");
System.out.println("creat new finalData");
finalData nfd = new finalData();
fd.show("fd");
nfd.show("nfd");
}
}
/*顯示fd : iiii =2 , iiiii = 3
creat new finalData
fd : iiii =2 , iiiii = 3
nfd : iiii =1 , iiiii = 3*/其中因為iiii是非static的,因此每次的值不相同。static的final數據只會在裝載的時候進行初始化,不會再每次產生新對象時再被初始化一次
空白的final
final同時允許將數據成員聲明為final的,但是並不初始化,但是,你必須在使用該成員數據之前保證該數據成員的初始化。這樣做的好處是既保證了數據的恆久不變性,
又可以根據對象的不同產生不同的final數據,具有更大的靈活性。
class finalTest
{
final String s;
finalTest(String s)
{
this.s=s;
System.out.println(this.s);
}
public static void main(String[] args)
{
new finalTest("hello");
new finalTest("bye");
}
}
final的引數
當final的引數是基本數據類型的時候,其值不允許改變。而當是對象時,則不允許將改變句柄的指向。
final的函數
使用final函數的意義有二:
1、鎖住該函數,不允許子類進行改變,也就是不能覆寫
2、讓短小的函數提高執行效率
final和private
class中private的函數其實就是天生的final函數,因為你無法使用private函數,當然也就無法覆寫該函數,而當你嘗試覆寫他的時候,其實是寫了一個全新的函數,把final加在private函數上,不具備任何意義!
final的class
當你確切的希望該class不能被繼承的時候,就需要用到final,然而無論class是否是final的,class中的數據成員既可以是final的也可以不是
初始化以及class的裝載
Java中只有在必要的時候才會裝載相關類,一般來說是class的初次使用和繼承時裝載,所謂初次使用,不僅僅是產生對象的時候,也有可能是某個static函數或者數據成員被使用的時候。首次使用class的這個時間點,也是static初始化的時候,任何static的class成員被裝載時,會根據他們出現的次序分別初始化,並且只會進行一次初始化。
繼承和初始化
下面的這個程序展示了繼承和裝載的順序
class base
{
int i = 10;
int j ;
base() //6、執行base構造函數
{
show(" i = "+i+", j = "+j);
j=20;
}
static int m = show("static base.m init"); //2、進行靜態成員的初始化以便裝載
public static int show(String s)
{
System.out.println(s);
return 30;
}
}
class derived extends base
{
int k =show("derived.k init"); //7、進行基本數據類型的初始化
derived() //8、已經完成數據成員的初始化動作,開始執行函數
{
show(" k = "+ k);
show(" j = "+j);
}
static int n =show("static derived.n init"); //3、進行靜態成員的初始化以便裝載
public static void main(String[] args) //1、啟用derived.main()靜態方法,class derived被使用,引起裝載器啟動,發現extends,於是裝載base
{
show("derived constructor"); // 4、裝載完畢,開始執行程序
derived d = new derived(); //5、開始產生對象,由於extends的關系,先產生base對象
}
}
輸出/*static base.m init
static derived.n init
derived constructor
i = 10, j = 0
derived.k init
k = 30
j = 20*/
總結
繼承和組合都允許你根據已經有的類產生新類。使用組合來重復運用以有類,使其成為新類中的一部分;繼承則用於接口的復用,由於子類繼承了父類的接口,因此可以向上轉型,這對多態是極為重要的。推薦多使用組合,組合具有彈性,繼承具有靈活性。