<a></a>
在jdk5.0以前,override要求參數清單和傳回值必須完全相同,否則編譯不通過,是以在jdk 1.3、 1.4裡面,這個代碼是錯誤的。 test裡面的newinstance 的傳回值必須修改為為父類完全相同的base才可以。
而在jdk1.5以後,系統允許傳回值和父類不同了,但必須是其子類才可以。這個問題我也是在實際程式設計時才注意到的。
這段程式傳回2,而不是3
有一段解釋的很清楚:
簡單翻譯為:
而上面這段輸出的則是3,因為如果finally中有return就直接傳回。
上面的結果大概很多人都猜錯了。-127到128之間的值是不可變的wrapper類型,是以vm确實對i1與i2使用了同樣的對象執行個體(以及記憶體位址),是以,==運算的結果是true。我們必須要注意此事,因為它會引發一些古怪又非常難以判别的bug。
大家是不是覺得單例函數永遠隻能傳回一個對象呢?其實利用反射可以輕松傳回n個不同的對象。java的反射破壞單例的私有構造函數保護,最典型的就是spring的bean注入,我們可以通過改造私有構造函數來防止。在singleton中,我們隻對外提供工廠方法(擷取單例),而私有化構造函數,來防止多面多餘的建立。對于一般的外部調用者來說,私有構造函數已經很安全了。
下面隻是一個簡單的例子(不考慮線程安全等因素,隻是為了簡潔)
一般的正常反射也是找不到私有構造函數的,但是我們隻要使用declaredconstructors方法和特權setaccessible方法即可執行個體化。
如果要防禦這樣的反射侵入,可以修改構造函數,加上第二次執行個體化的檢查。
另外,在spring的bean注入中,即使你私有化構造函數,預設他還是會去調用你的私有構造函數去執行個體化。(通過beanfactory來裝配bean,和上面的邏輯如出一轍)
是以,如果我們想保證執行個體的單一性,就要在定義時加上factory-method=””的屬性,并且在私有構造函數中添加防禦機制。單例的getinstance()可能會添加一些邏輯,而spring的預設調用構造函數去建立,就不能保證這份邏輯的準确性,是以會帶來隐患。
我們可以通過scope=”prototype”來測試單例是否被多次建立:
防禦機制生效,抛出 <code>can't create another instance</code> 異常,證明spring能正常調用私有的構造函數來建立bean,并且建立了多次。
這時候我們要使用factory-method來指定工廠方法,才能達到我們想要的效果
在每個覆寫了equals方法的類中,也必須覆寫hashcode方法。如果不這樣做的話,就會違反object.hashcode的通用約定,進而導緻該類無法結合所有基于散列的集合一起正常運作,這樣的集合包括hashmap、hashset和hashtable。
上面是一個完成了hashcode的簡單例子,例子當中為什麼選擇31,因為它是一個奇素數。如果乘數是偶數,并且懲罰溢出的話,資訊就會丢失,因為與2相乘等價于移位運算。31有個很好的特性,即用移位和減法來替代乘法,可以得到更好的性能:31*i == (i<<5)-i。現代的vm可以自動完成這種優化。
如果一個雷是不可變的,并且計算散列碼的開銷也比較大,就應該考慮把散列嗎緩存在對象内部,而不是每次請求的時候都重新計算散列碼。如果你覺得這種類型的大多數對象會被用作散列鍵(hash keys),就應該在建立執行個體的時候計算散列碼。否則,可以選擇“延遲初始化(lazily initialize)”散列碼,一直到hashcode被第一次調用的時候才init.
這種方法能夠獲得相當好的散列函數,但是它并不能産生最新的散列函數。
你可能希望打出的結果是
但是确實列印了unknown collection三次,這是為什麼呢?因為classify方法被重載了,而要調用哪個重載方法是在編譯時做出決定的。
這個程式的行為有悖常理,因為對于重載方法的選擇是靜态的,而對于重寫的方法的選擇是動态的
下面就是一個重寫的例子:
這個程式就是按照我們所想的列印出 wine sparkling wine champagne
到底怎樣才算胡亂使用重載機制呢?這個問題仍有争議。安全而保守的政策是,永遠不要導出兩個具有相同參數數目的重載方法。如果方法使用可變參數,保守的政策是根本不要重載它,如果你遵守這些限制,程式員永遠也不會陷入到“對于任何一組實際的參數,哪個重載方法是适用的”這樣的疑問中。這項限制并不麻煩,因為你始終可以給方法其不同的名稱,而不使用重載機制
float和double類型尤其不适用于貨币計算,因為要讓一個float或者double精确地表示0.1是不可能的。
這時候我們就要使用bigdecimal了。
關于bigdecimal是如何計算的,我以論壇中一個人的提問文章為例,來簡單的寫出bigdecimal的運算方法。題目是:李白無事街上走,提壺去買酒。遇店加一倍,見花喝一鬥,五遇花和店,喝光壺中酒,試問李白壺中原有多少鬥酒?
下面是一個bigdecimal的工具類:
上面這個程式被通常用來代替thread.stop()方法來停止線程。你可能期待這個程式運作一秒就暫停了,确實我們這裡1秒後把stoprequested設為true,但是結果并沒有暫停,而是永遠不會停止。
問題在于:由于沒有同步,就不能保證背景賢臣何時看到主線程對stoprequested的值所做的改變。沒有同步,虛拟機将這個代碼:
轉變為:
這種優化稱作提升(hoisting),正式hopspot server vm的工作。結果是個活性失敗(liveness failure),修改這個問題使用同步就可以搞定了。
但是我們還有更簡潔的寫法,給變量加個volatile關鍵字即可。雖然volatile修飾符不執行互斥通路,但它可以保證任何一個線程在讀取該域的時候都将看到最近剛剛被寫入的值:
我們也可以使用atomicboolean來實作,其實它的内部也是使用了volatile關鍵字:
現在我們有兩個類,一個parent,一個children,children繼承parent,如下所示:
大家猜想一下結果是什麼呢?
是不是和你們想的都不太一樣呢?
下面來給大家解釋一下:
1、jvm加載children的main方法前,要看children中是否有靜态的變量和語句,如果有,先給這些靜态的變量配置設定存儲空間和執行靜态語句(不是靜态方法),且由于children的父類中也有靜态的變量,根據繼承的特性,則先執行父類parent的靜态資料的初始化,然後執行子類的靜态資料的初始化。
2、執行main方法中的new children()語句,進行parent的類的執行個體化因為parent的靜态資料已經執行個體化,并且在一個執行過程隻執行個體化一次,是以在執行new parent()語句時,先執行非靜态變量定義的類的非靜态語句,之後再執行構造方法,所有有上面的結果。
總結如下:
(1)先靜态。具體是 父靜态 -> 子靜态
(2)先父後子。先父的全部,然後子的全部。
(3)優先級:父類 > 子類。靜态代碼塊 > 非靜态代碼塊 > 構造函數(與位置的前後無關系)
大家都看明白了嗎?
這個測試的輸出結果大家都寫對了嗎?
為什麼這麼輸出呢?因為for循環的執行步驟是先執行第一個分号前的函數,也就是’a’,再執行第二個分号前的函數,也就是’b’,如果符合條件,就執行for循環中的代碼,也就是’d’,接着執行第二個分号後的函數,也就是’c’,接下來,’a’永遠不會被執行了,因為它隻有第一次加載的時候才會執行,是以直接執行’b’,’d’,’c’的循環,知道不滿足第二個分号的函數,程式退出。
看到這裡,你是否對java最基礎的for循環有了更深的認識了呢?