JAVA編程思想(4),java編程思想
若干個對象共用
- 例如Frog對象擁有其自己的對象,並且知道他們的存活多久,因為Frog對象知道何時調用dispose()去釋放其對象。然而,如果這些成員對象中存在於其他一個或多個對象共用的情況,問題將不再簡單,不再能簡單的調用dispose()了。在這種情況下,我們也許需要引用計數來跟蹤依舊訪問著共用對象的數量。
//: polymorphism/ReferenceCounting.java// Cleaning up shared member objects.import static net.mindview.util.Print.*;class Shared { private int refcount = 0; private static long counter = 0; private final long id = counter++; public Shared() { print("Creating " + this); } public void addRef() { refcount++; } protected void dispose() { if(--refcount == 0) print("Disposing " + this); } public String toString() { return "Shared " + id; }}class Composing { private Shared shared; private static long counter = 0; private final long id = counter++; public Composing(Shared shared) { print("Creating " + this); this.shared = shared; this.shared.addRef(); } protected void dispose() { print("disposing " + this); shared.dispose(); } public String toString() { return "Composing " + id; }}public class ReferenceCounting { public static void main(String[] args) { Shared shared = new Shared(); Composing[] composing = { new Composing(shared), new Composing(shared), new Composing(shared), new Composing(shared), new Composing(shared) }; for(Composing c : composing) c.dispose(); }} /* Output:Creating Shared 0Creating Composing 0Creating Composing 1Creating Composing 2Creating Composing 3Creating Composing 4disposing Composing 0disposing Composing 1disposing Composing 2disposing Composing 3disposing Composing 4Disposing Shared 0*///:~
- static long counter跟蹤所建立的Shared的執行個體的數量,id是final的,因為我們不希望它的值在對象生命週期中被改變。
- 在將一個共用對象附著到類上時必須記住調用addRef(),但是dispose()方法跟蹤引用數,並決定何時執行清理。
構造器內部的多態方法行為
- 如果在一個構造器的內部調用正在構造的對象的某個動態Binder 方法是會發生什嗎?
- 在一般的方法內部,動態綁定的調用是在運行時才決定的,因為對象無法知道它是屬於方法所在的那個類,還是屬於那個類的匯出類。
- 如果要調用構造器內部的一個動態Binder 方法,就要用到那個方法的被覆蓋後的定義。然而,這個調用效果可能相當難於預料,因為被覆蓋的方法在對象被完全構造之前就被調用。這可能會造成一些難於發現的錯誤。
- 簡單來說就是在基類的構造器中調用基類中函數,這個函數也在匯出類中被覆蓋,當在匯出類調用基類構造器的時候,那麼因為一個動態綁定的方法調用會向外深入繼承階層內部,它可以調用匯出類裡的方法。那麼可能會調用某個方法,而這個方法所操作的成員可能還未進行初始化。
//: polymorphism/PolyConstructors.java// Constructors and polymorphism// don't produce what you might expect.import static net.mindview.util.Print.*;class Glyph { void draw() { print("Glyph.draw()"); } Glyph() { print("Glyph() before draw()"); draw(); print("Glyph() after draw()"); }} class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; print("RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { print("RoundGlyph.draw(), radius = " + radius); }} public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); }} /* Output:Glyph() before draw()RoundGlyph.draw(), radius = 0Glyph() after draw()RoundGlyph.RoundGlyph(), radius = 5*///:~
- Glyph.draw()方法設計將會被覆蓋,這種覆蓋是在RoundGlyph中發生的。但是Glyph構造器會調用這個方法,結果導致了對RoundGlyph.draw()的調用,這似乎是我們的目的,但是輸出的結果卻不是我們預計的,我們發現當Glyph()的構造器調用draw()方法時,radius不是預設初始值1,而是0。
- 我們先來看看初始化的實際過程:
- 在其他任何食物發生之前,將分配給對象的儲存空間初始化為二進位的零。
- 如以前說的一樣會調用基類構造器。此時,調用被覆蓋的draw()方法(要在調用RoundGlyph構造器之前調用),由於步驟1的緣故,我們此時會發現radius的值為0。
- 按照聲明的順序調用成員的初始化方法。
- 調用匯出類的構造器主體。
- 這樣做有一個優點,那就是所有東西都至少初始化為零。而不是僅僅留作垃圾。對象的引用通常為null,所以如果忘記為該引用進行初始化,就會在運行時出現異常。
- 因此,編寫構造器的時候有一條準則:“用儘可能簡單的方法使對象進入正常狀態,可以的話,避免調用其他方法。”在構造器唯一能夠安全調用的那些方法是基類中的final方法。
用繼承進行設計
- 多態並不是用來使得任何東西都可以被繼承的,因為這反倒會加重我們的設計負擔,使事情變得不必要的複雜起來。
- 更好的方式是選擇“組合”,尤其是不能十分確定應該使用哪一種方式時。組合更加靈活,因為它可以動態選擇類型(因此也就選擇了行為);相反,繼承在編譯時間需要知道確切類型。
//: polymorphism/Transmogrify.java// Dynamically changing the behavior of an object// via composition (the "State" design pattern).import static net.mindview.util.Print.*;class Actor { public void act() {}}class HappyActor extends Actor { public void act() { print("HappyActor"); }}class SadActor extends Actor { public void act() { print("SadActor"); }}class Stage { private Actor actor = new HappyActor(); public void change() { actor = new SadActor(); } public void performPlay() { actor.act(); }}public class Transmogrify { public static void main(String[] args) { Stage stage = new Stage(); stage.performPlay(); stage.change(); stage.performPlay(); }} /* Output:HappyActorSadActor*///:~
- 在這裡,Stage對象包含一個對Actor的引用,而Actor被初始化為HappyActor對象。這意味著performPlay()會產生某種特殊的行為。而當我們替換actor的對象引用的話,就可以改變我們的行為了。
- 一條準則是:“用繼承表達行為的差異,並用欄位來表達狀態上的變化。”
純繼承與擴充
- 採用“純粹”的方式("is-a")來建立繼承階層似乎是最好的方式。也就是說,只有基類已經建立的方法才可以在匯出類中被覆蓋。也可以說是匯出類“只有”覆蓋基類的函數沒有其他的函數。
- 因為一個類的介面已經確定了它應該是什麼,繼承可以確保所有的匯出類具有基類的介面,且絕不會少。“純粹”的繼承,匯出類將具有和基類一樣的介面。
- 也可以認為這是一種純替代,因為匯出類可以完全代替基類,而在使用它們時,可以不需要知道關於子類的任何額外資訊。也就是說,基類可以接收發送給匯出類的任何資訊,因為二者具有完全相同的介面。我們只需知道從匯出類向上轉型,永遠不需要知道正在處理的對象的確切類型。這都是通過多態來處理的。
- 但是匯出類往往不是只單純的只具有從基類繼承的介面,它還有自己的額外函數,我們可以稱為是(is-like-a)(像一個)關係,因為匯出類就像是一個基類——它有著相同的介面,但是它還具有由額外方法實現的其他特性。
- ”像一個“關係也是有缺點的。匯出類中介面的擴充部分不能被基類訪問。因此,一旦我們向上轉型,就不能調用那些新方法。在這種情況下,我們一般是重新查看對象的確切類型,以便我們能夠訪問該類型所擴充的方法。
向下轉型與運行時類型識別
- 由於向上轉型會丟失具體的類型資訊,所以我們想,通過向下轉型來擷取類型資訊。然後我們知道向上轉型是安全的,因為基類的介面是不會大於匯出類的介面的。因此,我們通過基類的介面發送的資訊保證都能被接受。但是對於向下轉型,我們無法知道我們它將轉型是哪種類型。
- 在Java中,所有轉型都會得到檢查!所以即使我們只是進行一次普通的加括弧形式的類型轉型,在進入運行時仍會進行檢查,如果不是正確的類型,會返回一個ClassCastException(類轉型異常)。這種在運行時期對類型進行檢查的行為稱作“運行時類型識別”(RTTI)。
//: polymorphism/RTTI.java// Downcasting & Runtime type information (RTTI).// {ThrowsException}class Useful { public void f() {} public void g() {}}class MoreUseful extends Useful { public void f() {} public void g() {} public void u() {} public void v() {} public void w() {}} public class RTTI { public static void main(String[] args) { Useful[] x = { new Useful(), new MoreUseful() }; x[0].f(); x[1].g(); // Compile time: method not found in Useful: //! x[1].u(); ((MoreUseful)x[1]).u(); // Downcast/RTTI ((MoreUseful)x[0]).u(); // Exception thrown }} ///:~
java編程思想第四版
你就在百度文庫裡面搜就行啊
這個是搜尋出來的結果
wenku.baidu.com/...0&od=0
java編程思想 第四版 中文版 紙質書與網上的電子書為何不一樣?
因為網上的JAVA編程思想第四版的電子書,大部分都是假的,只是第四版的封面加上了第三版的內容。你的紙質書是正確的,第七章是複用類,第八章是多態。我的紙質書跟你的一樣,放心吧。不過不明白為什麼第四版沒有了網路編程,不知道作者怎麼想的。我們捨得花錢買書,作者還捨不得紙張不成?不理解。