類與對象之類的複用(繼承、組合、代理)
作者:egg
微博:http://weibo.com/xtfggef
出處:http://blog.csdn.net/zhangerqing
此章我們主要談下物件導向編程的代碼複用機制。
繼承
繼承是OOP中最為重要的概念,達到了非常有效代碼重用效果,使得開發效率變得很高!同時也因此,造成了OOP語言執行效率低下,不免被C/C++程式員嘲笑。在Java語言中,兩個類之間通過extends關鍵字實現繼承。我們來看個繼承的執行個體:
class A {public A() {System.out.println("A()!");}}class B extends A {public B() {System.out.println("B()!");}}public class ExtendsTest extends B {public ExtendsTest() {System.out.println("ExtendsTest()!");}public static void main(String[] args) {new ExtendsTest();}}
ExtendsTest繼承自B,B繼承自A,當執行個體化ExtendsTest的時候,卻依次列印出了A、B、ExtendsTest構造器中的內容,說明:構造器被依次調用了,這是為什嗎?因為當類實現繼承時,預設的會將基類的一個子物件傳給子類,而子類需要對這個子物件進行初始化,所以需要調用父類的構造器,但是,這一切都是隱式進行的,我們看不到,不過可以從實驗中得出結論:在對子類進行初始化的時候,會先調用父類的構造器(如果有學過C++的同學,肯定知道,在C++中除了有建構函式,還有解構函式,初始化的時候先調用父類的建構函式,析構的時候,先析構子類對象,再析構父類對象,一個從外到裡,再由裡到外的過程)。如果父類構造器需要傳遞參數,則使用super關鍵字來實現就行了。
class B extends A {public B(int n) {System.out.println("B()!");}}public class ExtendsTest extends B {public ExtendsTest(int n) {super(n);System.out.println("ExtendsTest()!");}public static void main(String[] args) {new ExtendsTest(1);}}
下面我們分幾種情況討論下繼承:
1、子類不能繼承父類私人的域或者方法。如果想要繼承父類的私人對象,只能將private改成protected,因為protected的許可權控制在包內。因此一般情況,用到繼承的話,最好將父類中的域聲明為私人(private,因為一般情況不需要繼承成員變數),將方法聲明為public,方便繼承。
2、當子類對象調用一個方法時,如果子類沒有,則去調用父類的同名方法,但是調用者保持是子類。
public class A {int a = 10;void a(){System.out.println(a);System.out.println(getClass().getName());}}class B extends A {int a = 20;//void a(){//System.out.println(a);//System.out.println(getClass().getName());//System.out.println(this.a);//System.out.println(super.a);//}public static void main(String[] args) {B b = new B();b.a();}}
輸出:
10
B
a()B中被注釋掉了,則調用的是父類A中的,所以輸出的值是A中的成員變數。但是調用getClass()擷取的仍然是B。當我們將上述代碼中的注釋去掉,則輸出:
20
B
20
10
當B中有a()方法時,屏蔽了A中的a(),super關鍵字調用的是父類的資訊,this關鍵字調用的是當前類的資訊。
代理
代理的思想在我們講得設計模式裡面有體現,就是在一個類中持有另一個類的執行個體,從而代替原類進行一個操作,我們看個例子:
public class ProxyTest {Source source = new Source();void p(int n){source.a(n);}void p2(int n){source.b(n);}public static void main(String[] args) {ProxyTest pt = new ProxyTest();pt.p(20);pt.p2(50);}}class Source{void a(int n){System.out.println("this is : "+n);}void b(int n){System.out.println("this is : "+n);}}
組合
如果大家還記得設計模式裡的建造者模式,那麼很容易聯想到組合機制,就是將一系列的對象組合在一起,組合成一個功能豐富的類,當然,這些對象包括基礎資料型別 (Elementary Data Type),也包括引用。來看個例子:
class Soap{private String s;Soap(){System.out.println("soap");s = "constructor";}public String toString(){return s;}}public class CompronentTest {private String s1 = "happy",s2="Happy",s3,s4;private Soap castille;private int i;public CompronentTest(){s3 = "joy";castille = new Soap();}{i = 88;}public String toString(){if(s4 == null){s4 = "Joy";}return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n" + "castille = " + castille;}public static void main(String[] args) {CompronentTest ct = new CompronentTest();System.out.println(ct);}}
該類就是一個普通的組合類別,在組合類別中我們應該注意這個對象的初始化方式,此處:1、s1和s2採用在聲明的地方直接賦值,這樣能夠保證它們在構造器被調用之前被初始化(詳細可見類與對象一中關於類的初始化順序的介紹)。2、s3在構造器中初始化。3、s4採用的是懶載入(下面會講)。4、i在非靜態初始化塊中。此處我們說下toString方法,就是一個將其它對象轉為String對象的方法,除了非基本類型的對象,其它都有一個toString方法,這是因為toString方法是Object類的固有方法,在Java中任何類都隱式繼承Object類,也就說都隱含toString方法。所以,在上述的例子中,當最後的字串+ castille對象時,需要將castille對象以字串的形式表現出來,因此調用了toString()。
懶載入因為涉及持有對象執行個體,所以會涉及到懶載入的機制,代碼中的:
if(s4 == null){
s4 = "Joy";
}
就是一種懶載入的機制,這種機制就是解決當所需的對象比較龐大的時候,只有在用的時候才去初始化,節省空間的,提高效率!
總結下:
1、初始化方面,注意一些特殊對象的初始化,可以在定義的時候直接初始化,或者在構造方法中或在方法塊中,或者在用的時候懶載入。
2、toString方法,解決和字串對象銜接出現的類型不符問題。
3、懶載入,提高效率,對於大的對象,消極式載入!
補充:
Java對中文類名、方法名的支援。
public class Test {public static void main(String[] args) {學生 我 = new 學生();我.說();}}class 學生{public void 說(){System.out.println("hello everyone!");}}
補充一點兒東西,Java中的運算子優先順序:
補充:Java中跳出迴圈的方法:
眾所周知,在Java中,如果想跳出for迴圈,一般情況下有兩種方法:break和continue。
break是跳出當前for迴圈,如下面代碼所示:
package com.xtfggef.algo;public class RecTest {/** * @param args */public static void main(String[] args) {for(int i=0; i<10; i++){if(i==5){break;}System.out.print(i+" ");}}}
輸出:0 1 2 3 4
也就是說,break會跳出(終止)當前迴圈。
continue是跳出當前迴圈,開使下一迴圈,如下所示:
package com.xtfggef.algo;public class RecTest {/** * @param args */public static void main(String[] args) {for (int i = 0; i < 10; i++) {if (i == 5) {continue;}System.out.print(i+" ");}}}
輸出:0 1 2 3 4 6 7 8 9
以上兩種方法沒有辦法跳出多層迴圈,如果需要從多層迴圈跳出,則需要使用標籤,定義一個標籤label,然後再需要跳
出的地方,用break label就行了,代碼如下:
package com.xtfggef.algo;public class RecTest {/** * @param args */public static void main(String[] args) {loop: for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {for (int k = 0; k < 10; k++) {for (int h = 0; h < 10; h++) {if (h == 6) {break loop;}System.out.print(h);}}}}System.out.println("\nI'm here!");}}
輸出:
012345
I'm here!
意思很顯然!
持續更新中...