[Java] 複用類

1. 組合語法

在新的類中產生現有類的對象,這種方法稱爲組合,該方法只是複用了現有程序代碼的功能,而非它的形式,例如:

class Test1 {
  public void sayHello() {
    System.out.println("Hello World");
  }
}
class Test2 {
  private Test1 t1 = new Test1();
  public void getString() {
    t1.sayHello();
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.getString();
  }
}

每一個非基本類型的對象都有一個toString()方法,而且當編譯器需要一個String而卻只有一個對象時,該方法便會被調用,例如:

class Test1 {
  public String toString() {
    return "Hello World";
  }
}
class Test2 {
  private Test1 t1 = new Test1();
  public void getString() {
    System.out.println("Say: " + t1);
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.getString();
  }
}


2. 繼承語法

繼承是所有OOP語言和Java語言不可缺少的組成部分。當創建一個類時,如果沒有明確指出要從其他類中繼承,那麼就是在隱式地從Java標準根類Object進行繼承。在繼承過程中,需要用關鍵字extends實現,例如:

class Test1 {
  public void sayHello() {
    System.out.println("Hello World");
  }
  public void sayBye() {
    System.out.println("Bye");
  }
}
class Test2 extends Test1 {
  public void sayHello() {
    super.sayHello();
    sayBye();
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.sayHello();
  }
}

由於Test2是由關鍵字extends從Test1導出的,它可以自動獲得Test1中非private的方法,因此,可以將繼承視作是對類的複用。修改基類中定義的方法是可行的,Java用super關鍵字表示超類,可以在導出類中使用super來訪問基類版本的方法。

如果一個程序中含有多個類,可以爲每個類設置一個main()方法,這種技術可使每個類的單元測試變得簡便。只有命令行所調用的那個類的main()方法會被調用,比如命令行是"java Test1",那麼Test1.main()將會被調用,即使不是public類,public main()方法仍然會被調用,例如:

class Test1 {
  public void sayHello() {
    System.out.println("Hello World");
  }
  public void sayBye() {
    System.out.println("Bye");
  }
  public static void main(String[] args) {
    Test1 t1 = new Test1();
    t1.sayHello();
    t1.sayBye();
  }
}
class Test2 extends Test1 {
  public void sayHello() {
    super.sayHello();
    sayBye();
  }
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.sayHello();
    t2.sayBye();
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.sayHello();
  }
}

導出類就像是一個與基類具有相同接口的新類,或許還有一些額外的方法和域,當創建一個導出類的對象時,該對象包含了一個基類的子對象,子對象被包裝在導出類對象內部,並會被恰當初始化,例如:

class Test1 {
  public Test1() {
    System.out.println("Test1 constructor");
  }
}
class Test2 extends Test1 {
  public Test2() {
    System.out.println("Test2 constructor");
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
  }
}

執行結果是先後輸出"Test1 constructor"和"Test2 constructor",可以發現構建過程是從基類向外擴散的,所以基類在導出類構造器可以訪問它之前,就已經完成了初始化,即使不爲Test1創建構造器,編譯器也會合成一個默認的構造器。


3. 代理

除了組合和繼承外,代理是第三種關係,介於繼承與組合之間。如果將一個成員對象置於所要構造的類中,與此同時我們在新類中暴露了該成員對象的所有方法以,例如:

public class SpaceShipControls {
  void up(int velocity) {}
  void down(int velocity) {}
}
public class SpaceShip extends SpaceShipControls {
  public static void main(String[] args) {
    SpaceShip ship = new SpaceShip();
    ship.up(100);
  }
}

SpaceShip包含SpaceShipControls,與此同時,SpaceShipControls的所有方法在SpaceShip中都暴露了出來,代理解決了此問題,例如:

public class SpaceShipControls {
  void up(int velocity) {}
  void down(int velocity) {}
  void reboot() {}
}
public class SpaceShip extends SpaceShipControls {
  private SpaceShipControls controls = new SpaceShipControls();
  public void up(int velocity) {
    controls.up(velocity);
  }
  public void down(int velocity) {
    controls.down(velocity);
  }
  public static void main(String[] args) {
    SpaceShip ship = new SpaceShip();
    ship.up(100);
  }
}


4. final

根據上下文環境,Java的關鍵字final的含義存在細微的區別,但通常指的是“無法改變的”。不想做改變可能出於兩種理由:設計或效率。

許多編程語言都有某種方法告知編譯器一塊數據是恆定不變的,比如一個永不改變的編譯時常量或一個在運行時被初始化的值,而不希望它被改變。對於編譯期常量的情況,常量必須是基本數據類型,並且以關鍵字final表示,在對這個常量進行定義的時候,必須對其進行賦值。一個既是static又是final的域只佔據一段不能改變的存儲空間。當對對象引用而不是基本類型運用final時,表示final使引用恆定不變,一旦引用被初始化指向一個對象,就無法再指導它改爲指向另一個對象,然而對象自身卻是可以被修改的,例如:

class Value {
  int i;
  public Value(int i) {
    this.i = i;
  }
}
public class FinalData {
  private final int i = 10;
  private final Value v = new Value(20);
  public static void main(String[] args) {
    FinalData fd = new FinalData();
    // Error : fd.i++
    fd.v.i++;
    System.out.println(fd.i);
    System.out.println(fd.v.i);
  }
}

Java允許生成空白fina。所謂空白final是指被聲明爲final但又未給定初值的域,無論什麼情況,編譯器都確保空白final在使用前必須被初始化,但是,空白final在關鍵字final的使用上提供了更大的靈活性,例如:

class Poppet {
  private int i;
  Poppet(int i) {
    this.i = i;
  }
}
public class BlankFinal {
  private final int i;
  private final Poppet p;
  public BlankFinal(int x) {
    i = x;
    p = new Poppet(x);
  }
  public static void main(String[] args) {
    new BlankFinal(10);
  }
}

Java允許在參數列表中以聲明的方式將參數指明爲final,這意味着你無法在方法中更改參數引用所指向的對象,例如:

class Gizmo {
  public void spin() {}
}
public class FinalArguments {
  void with(final Gizmo g) {
    // Error : g = new Gizmo();
  }
  void without(Gizmo g) {
    g = new Gizmo();
    g.spin();
  }
  public static void main(String[] args) {
    FinalArguments fa = new FinalArguments();
    fa.without(null);
    fa.with(null);
  }
}

使用final方法可以把方法鎖定,防止任何繼承類修改它的含義。類中所有的private方法都隱式地指定爲final的,可以對private方法添加final修飾詞,但這並不能給方法增加任何額外含義,例如:

class Test1 {
  final void f() {}
}
class Test2 extends Test1 {
}
public class Main {
  public static void main(String[] args) {
    Test2 t = new Test2();
    t.f();
  }
}

當將某個類整體定義爲final時,表示不能繼承該類,例如:

final class Test {
  void f() {}
}
public class Main {
  public static void main(String[] args) {
    Test t = new Test();
    t.f();
  }
}

final類的域可以根據個人意願選擇爲是或不是final,然後final類中所有的方法都隱式指定爲是final的,因爲無法覆蓋它們。



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