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類繼承産生一個具體類型的“甲殼蟲”。其形式與現有類相同,跟蹤并解釋其輸出結果。
意義不明。略。