Thinking in java 第7章 複用類
7.1 組合語法
1. 初始化引用的位置:
- 定義對象的地方。這意味着它們總能夠在構造器被調用之前被初始化。
- 在類的構造器中。
- 正要使用這些對象之前,這種被稱爲惰性初始化。在生成對象不值得及不必每次都生成對象的情況下,這種方式可以減少額外的負擔。
- 使用實例初始化。
7.2 繼承語法
1. 當創建了一個導出類的對象時,該對象包含了一個基類的子對象。這個子對象與你用基類直接創建的對象是一樣的。區別在於,後者來自與外部,而基類的子對象被包裝在導出類對象內部。
2. 構建過程是從基類向外擴散的,所以基類在導出類構造器可以訪問它之前,就已經完成了初始化。
7.3 代理
1. 代理是繼承與組合之間的中庸之道,避免基類的方法在導出類中全部暴露出來(在導出類中創建基類的private對象,並通過導出類的方法調用基類方法)。
7.8 final關鍵字
1. 對於基本類型,final使數值恆定不變;對於對象引用,final使引用恆定不變,然而對象其自身是可以修改的。
2. 對於空白final(即定義時未初始化),要在構造函數中進行初始化。
3. final參數,意味着無法在方法中更改參數引用所指向的對象。
4. final方法,鎖定方法,以防任何繼承類修改它的含義。過去還可能因爲效率,不過一部分被優化了。
5. private方法都隱式地指定爲是final,而private方法可以被覆蓋。
6. final類,太監類。
習題
練習1:創建一個簡單的類。在第二個類中,將一個引用定義爲第一個類的對象。運用惰性初始化來實例化這個對象。
package Chapter7;
public class E1 {
E1temp e;
void func() {
e = new E1temp();
e.setS("aaa");
System.out.println(e.getS());
}
public static void main(String[] args) {
E1 e = new E1();
e.func();
}
}
class E1temp{
private String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
練習2:從Detergent中繼承產生一個新的類。覆蓋scrub()並添加一個名爲sterilize()的新方法。
extend再@override一下即可。略。
練習3:證明前面這句話(指構建過程是從基類向外擴散的,所以基類在導出類構造器可以訪問它之前,就已經完成了初始化)。
即P129的代碼。略。
練習4:證明基類構造器:(a)總是會被調用;(b)在導出類構造器之前被調用。
同上。略。
練習5:創建兩個帶有默認構造器(空參數列表)的類A和類B。從A類中繼承產生一個名爲C的新類,並在C內創建一個B類成員。不要給C編寫構造器。創建一個C類對象並觀察其結果。
package Chapter7;
public class E5 {
public static void main(String[] args) {
E5C e = new E5C();
}
}
class E5A{
E5A() {
System.out.println("E5A has been built");
}
}
class E5B{
E5B() {
System.out.println("E5B has been built");
}
}
class E5C extends E5A{
E5B e = new E5B();
}
/*
E5A has been built
E5B has been built
*/
練習6:證明前一段話(調用基類構造器必須是你在導出類構造器中要做的第一件事)。
若嘗試在非第一句調用基類構造器或基類構造器的默認構造器被覆蓋且未被重寫且在子類構造器中沒有添加時,會發生編譯錯誤。
練習7:修改練習5,使A和B以帶參數的構造器取代默認的構造器。爲C寫一個構造器,並在其中執行所有的初始化。
package Chapter7;
public class E5 {
public static void main(String[] args) {
E5C e = new E5C();
}
}
class E5A{
E5A(int i) {
System.out.println("E5A has been built " + i );
}
}
class E5B{
E5B(int i) {
System.out.println("E5B has been built " + i);
}
}
class E5C extends E5A{
E5B e;
E5C() {
super(1);
e = new E5B(2);
}
}
/*
E5A has been built 1
E5B has been built 2
*/
練習8:創建一個基類,它僅有一個非默認構造器;再創建一個導出類,它帶有默認構造器和非默認構造器。在導出類的構造器中調用基類的構造器。
同上,導出類中每個構造器都要調用基類的非默認構造器。
練習9:創造一個Root類,令其含有名爲Component1,Component2,Component3的類的各一個實例。從Root中派生一個類Stem,也含有上述各“組成成分”。所有的類都應帶有可打印的相關信息的的默認構造器。
package Chapter7;
public class E9 {
public static void main(String[] args) {
E9Stem stem = new E9Stem();
}
}
class E9Root{
E9Component1 e1 = new E9Component1(1);
E9Component2 e2 = new E9Component2(1);
E9Component3 e3 = new E9Component3(1);
public E9Root() {
System.out.println("Root ahs been built");
}
}
class E9Stem extends E9Root{
E9Component1 e1 = new E9Component1(2);
E9Component2 e2 = new E9Component2(2);
E9Component3 e3 = new E9Component3(2);
E9Stem() {
System.out.println("Stem has been built");
}
}
class E9Component1{
E9Component1(int i) {
System.out.println("E91 has been built " + i);
}
}
class E9Component2{
E9Component2(int i) {
System.out.println("E92 has been built " + i);
}
}
class E9Component3{
E9Component3(int i) {
System.out.println("E93 has been built " + i);
}
}
/*
E91 has been built 1
E92 has been built 1
E93 has been built 1
Root ahs been built
E91 has been built 2
E92 has been built 2
E93 has been built 2
Stem has been built
*/
練習10:修改練習9,使每個類都僅具有非默認的構造器。
同上。略。
練習11:修改Detergent.java,讓它使用代理。
即用私有對象取代繼承,並通過公共方法調用私有對象方法。略。
練習12:將一個適當的dispose()方法的層次結構添加到練習9的所有類中。
略。相當於方法的調用順序,先調用導出類的再調用基類。
練習13:創建一個類,它應帶有一個被重載了三次的方法。繼承產生了一個新類,並添加一個該方法的新的重載定義,展示這四個方法在導出類中都是可以使用的。
略。重載後可以直接使用。
練習14:在Car.java中給Engine添加一個service()方法,並在main()中調用該方法。
car.engine.service();
練習15:在包中編寫一個類,類應具備一個protected方法。在包外部,試着調用該protected方法並解釋其結果。然後,從你的類中繼承產生一個類,並從該導出類的方法內部調用該protected方法。
略。在包外部非子類不能調用protected方法,子類可以。
練習16:創建一個名爲Amphibian的類。由此集成產生一個稱爲Frog的類。在基類中設置適當的方法。在main()中,創建一個Frog向上轉型至Amphibian,然後說明所有方法都可以工作。
package Chapter7;
public class E16 {
public static void main(String[] args) {
E16Amphibian frog = new E16Frog();
frog.sound();
frog.crawl();
frog.swim();
//frog.eatFly();
}
}
class E16Amphibian{
void crawl() {
System.out.println("pa");
}
void swim() {
System.out.println("you");
}
void sound() {
System.out.println("not unique");
}
}
class E16Frog extends E16Amphibian {
void sound() {
System.out.println("gua");
}
void eatFly() {
System.out.println("Eat");
}
}
/*
gua
pa
you
*/
向上轉型後不能調用子類特有方法,但可以調用重寫方法。
練習17:修改練習16,使Frog覆蓋基類中的方法的定義(令新定義使用相同的方法特徵簽名【即方法的定義】)。請留心main()中都發生了什麼。
同上。略。
練習18:創建一個含有static final域和final域的類,說明二者間的區別。
package Chapter7;
public class E18 {
public static void main(String[] args) {
E18temp e1 = new E18temp("111");
E18temp e2 = new E18temp("111");
System.out.println(e1.j);
System.out.println(e2.j);
System.out.println(e1.i == e2.i);
System.out.println(e1.j == e2.j);
}
}
class E18temp{
final String i;
static final String j = "jjj";
E18temp(String a) {
this.i = new String(a);
}
}
/*
jjj
jjj
false
true
*/
static final類共享同一個地址,而final各用各的;static final類在第一次使用該類時就會被初始化,早於final。
即兩者都有final的特性,區別等於static和非static的區別。
練習19:建一個含有指向某對象的空白final引用的類。在所有構造器內部都執行空白final的初始化動作。說明Java確保final在使用前必須初始化,且一旦被初始化即無法改變。
同上。略。
練習20:展示@Override註解可以解決本節問題。
@Override若未正確重寫會報錯。略。
練習21:創建一個帶final方法的類。由此繼承產生一個類並嘗試覆蓋該方法。
略。
練習22:創建一個final類並試着繼承它。
略。
練習23:請證明加載類的動作僅發生一次。證明該類的第一個實體的創建或者對static成員的訪問都有可能引起加載。
package Chapter7;
public class E23 {
public static void main(String[] args) {
E23temp2 e1 = new E23temp2();
E23temp1 e2 = new E23temp2();
}
}
class E23temp1{
static{
System.out.println("AAA");
}
E23temp1() {
System.out.println("BBB");
}
}
class E23temp2 extends E23temp1{
static{
System.out.println("CCC");
}
E23temp2() {
System.out.println("DDD");
}
}
/*
AAA
CCC
BBB
DDD
BBB
DDD
*/
練習24:在Beetle.java中,從Beetle類繼承產生一個具體類型的“甲殼蟲”。其形式與現有類相同,跟蹤並解釋其輸出結果。
意義不明。略。