1. 程式人生 > >《JAVA解惑》學習筆記

《JAVA解惑》學習筆記

  • 不要用return、break、continue、throw來退出finally語句塊。這樣將直接跳出finally語句塊,從而跳過try語句塊,這並不是我們想要的效果。
  • 關於try-catch
    1. 如果一個catch子句要捕獲一個型別為E的受檢查異常,而其相對應的try子句卻不能丟擲E或其子類異常,這樣編譯時無法通過的。 try {} catch(IOException e){} // 非法,因為IOException是檢查異常
    2. 捕獲Exception或Throwable的catch子句是合法的,不管預期相對應的try子句的內容是什麼,哪怕為空 try {} carch(Exception e){} // 合法
    3. 一個方法可以丟擲的檢查異常集合是它所適用的所有型別宣告要丟擲的檢查異常集合的交集 即:假設介面1宣告方法f() throws A,介面2宣告方法f() throws B,實現類同時實現這兩個介面,必須實現方法f(),但是這裡寫成f()才對, 因為異常要取交集
  • 對於一個未賦值的final域或變數,在try和catch塊中同時為其賦值時是會報錯的,哪怕邏輯上OK。 因此在使用final域時,最好的方式是宣告同時賦值
  • System.exit()
    1. 將停止所有的程式執行緒。
    2. 該方法被呼叫時,會執行所有關閉掛鉤操作,釋放VM之外的資源 因此我們如果需要在System.exit()執行後釋放資源,可以呼叫Runtime.getRuntime().addShutdownHook(Thread t)來新增關閉鉤子
    3. 與之類似的方法 System.halt() 該方法會直接關閉所有執行緒,不執行關閉鉤子的動作,建議少用。
  • 在複習一遍例項化過程
    • 分配記憶體,並將所有域初始化為零值
    • 進入構造方法,首先按照域宣告順序進行初始化賦值
    • 再遞迴執行構造方法程式體
  • 構造器必須宣告其例項化操作會丟擲的所有檢查異常
    • 哪怕這些異常不是在構造器程式體中丟擲
    • 典型是的域宣告同時初始化時丟擲的異常,需要在構造器丟擲
  • finally中的關流問題 我們經常遇到輸入輸出流需要在finally中,但流的close()方法也會丟擲異常。
    • 解決方法是在finally中也丟擲異常,但這樣就不美觀了。
    • 好的解決方法是寫一個方法,傳入這些待關閉的流,使用時統一呼叫即可。一般來說,這些流都繼承了Closeable方法。
  • 對於任何在finally語句塊中丟擲的異常,我們都應該當場處理,而不是繼續丟擲去
  • 在執行迴圈時,不能依賴於異常跳出迴圈,這樣速度會非常慢
  • & | 操作符
    1. 正常情況是位與/或運算子
    2. 兩邊位boolean時,變成了邏輯與/或,只不過沒有短路功能。
  • Class.newInstance()會傳播從無參構造器丟擲的所有異常。
    • 比較坑爹的是我們在呼叫該方法時只能顯式地看到其宣告的兩個異常。對應的無參構造器會丟擲什麼異常我們並不知道。
  • Java的異常檢查機制不是虛擬機器強制執行的,只是編譯器提供的工具。因此編譯器給出的異常檢查警告一定要認真對待。
  • 探測類丟失
    1. 要想編寫一個能夠探測類丟失的程式,使用反射來引用類,而不要使用常規方法,具體原因可以參見 解惑44 雖然Java語言規範非常仔細地描述了類初始化是何時發生的,但是類被載入的時機卻是遠遠不可預測的,就像解惑44,居然是驗證時載入了類。
    2. 不要對捕獲NoClassDefFoundError形成依賴,一般來說,捕獲Error及其子類是非常不可取的。
  • Java的過載解析過程
    1. 選取所有可獲得並可應用的方法或構造器
    2. 在第一階段選取的方法或構造器選取最精確的一個 精確:如果一個方法可以接受傳遞給另一個方法的任何引數,我們就是該方法不如另一個方法精確。
    3. 要想強制要求編譯器選擇一個確定的過載版本,需要將實參轉型為形參所宣告的型別
  • 每一個靜態域在宣告他的類及其所有子類中共享一份單一的拷貝(這點要尤其注意,之前理解錯誤) 即:靜態域由宣告它的類及其所有子類所共享
  • 在選擇繼承還是組合的方式時,一定要充分考慮他們的關係是 is-a 還是 has-a,不能自己隨便用
  • 靜態方法
    1. 屬於類,不存在任何動態的分派機制(不會過載),呼叫時按引用型別決定呼叫的是哪個方法(編譯器繫結)。
    2. 一定不要通過物件來呼叫靜態方法,因為一不小心就會引起問題
  • 小記一筆
    1. 類的靜態域是在類載入後就進行初始化的,初始化順序和宣告順序一致
    2. 例項變數是在建立物件時進行初始化的,順序也和宣告的順序一致
  • instanceof操作符
    1. 當左操作符是null時,運算結果被定義為false
    2. 右操作符是不允許為null的
    3. 如果左右操作符都是類,則要求一個類必須是另一個類的子類 如 new Type() instanceof String, 要求Type必須是String的子類或父類
  • 一個final型別的例項域被賦值前,是有可能被取用的,語法上沒有問題
  • 應注意不要使用迴圈的類初始化和迴圈的例項初始化。
    1. 迴圈的類初始化:初始化靜態域時呼叫尚未初始化的靜態域
    2. 迴圈的例項初始化:初始化例項時呼叫尚未初始化的例項變數
  • 禁止在構造器中、偽構造器中呼叫重寫方法,因為這樣可能呼叫到子類的重寫方法,而此時重寫方法呼叫的值很可能尚未初始化好。
  • 靜態方法呼叫的限定表示式是可以計算的,但是它的值將被忽略。如(現實中不要這樣寫)
    1. ((Null)null).greet(); 是可以正常執行的,並不會報錯,其中greet是類Null的靜態方法
    2. null.greet(); 就是不可行的了,因為null屬於原始型別,不能這樣做,編譯無法通過。
  • 奇怪的問題
    1. 語言規範不允許一個本地變數宣告語句作為一條語句在for、while迴圈中重複執行,但是在語句塊中就可以
    2. 舉例 while(true) int i=0; // 編譯無法通過。 while(true) {int i=0;} // 編譯通過
  • 計數
    1. 執行緒安全的計數器,不要忘記使用AutomicLong哦
    2. int計數到溢位最短只需要21秒,long最短需要9x1010秒(按照每秒計數108算),因此計數最好使用long
  • 例項不可變
    1. String、BigDecimal、BigInteger、所有包裝類的例項都是不可變的。
    2. 即,我們不能修改現有例項的值,對這些例項的操作將產生一個新的例項
    3. 舉例:
      • String str = “e”; str.replace(‘e’,‘d’); //執行後str的值並不會改變,要想得到改變後的值:str = str.replace(‘e’,‘d’);
      • BigInteger i = new BigInteger(10); i.add(new BigInteger2.); //同樣,執行後不會有變化,這一項是比較容易忽略的,要記住。
  • 無論什麼時,只要我們重寫了equals()方法,就一定要同時重寫hashCode()方法 原因:HashSet會先根據hashCode()值找元素位置,再呼叫equals()判斷是否相等。
  • 重寫方法時,最好(必須)加上註解Override,這樣可以避免錯誤。
  • 八進位制以0(零)開頭,這一點記住了,012代表八進位制,12才是十進位制
  • 新API
    1. Arrays.deepToString() 能夠將多維陣列的每一項都列印
    2. 整形的包裝類支援通用的位處理操作:如bitCount(),統計被置位的位數
  • Java平臺的每一個主版本都在其類庫中隱藏了一些寶藏,因此研究Java的新特性頁面有很大好處。 此外,持續研究Java API也是有好處的。
  • 噁心的日期
    1. Calendar的月是從0開始的。
    2. 使用Calendar時,最好隨時翻看API,因為很多反人類的API會誤導我們。
  • IdentityHashMap:與其他類不同的是,該Map在比較key時,使用的是引用等價性比較,而不是常用的值比較
  • 取餘 -2%3 = 1 -2 = -13 + 1 -1為商,1為餘 2%3 = 2 1 = 03 + 2 0為商,2為餘
  • 負數的絕對值為負數的情況 Math.abs():當引數為MIN_VALUE時,由於整數的二進位制數不具有對稱性,因此最小值的絕對值還是為該值
  • 比較器的缺陷
    1. 使用Comparator時,我們需要重寫int compare(o1, o2)方法,一般直接返回o1-o2這樣的值,但是如果o1-o2發生了溢位, 可能得到相反的效果
    2. 解決方案是不要在重寫的compare中直接相減,更好的方法是比較,然後轉化為-1、0、1這樣的數
  • 要儘量避免重用平臺類的名字,千萬不要使用java.lang包中的類名,因為很多地方自然地用到這些類,當我們自己定義了同名類時,一不小心就讓這些地方用到了我們定義的名字,到時候哭死都找不出問題所在。
  • 隱藏
    1. 當子類與父類存在同名例項變數、靜態方法、成員型別時,子類會將可訪問到的父類的變數隱藏起來,但其依然存在,可以通過將子類物件轉為父類引用進行呼叫。
    2. 要避免隱藏,因為這可能導致混亂 遮掩
    3. 當一個變數和一個型別具有相同的名字,並且它們位於相同作用域時,變數名具有優先權
    4. 按照標準命名規範時,不會遇到這種問題。 遮蔽
    5. 指的是本地變數、內部類、方法等,因為與匯入的變數、類、方法等同名,而採用本地的變數、內部類、方法的情況,即本地遮蔽了匯入
    6. 更普遍地說:一個變數、方法或型別可以分別遮蔽在一個閉合的文字範圍內的具有相同名字的變數、方法、型別。
    7. 更形象地說:一個閉合範圍內,範圍1包含變數A和範圍2,範圍2包含同名變數A,則在範圍2中的A遮蔽了範圍1中範圍2外的A變數
  • 靜態內部類和非靜態內部類
    1. 靜態內部類能夠擁有自己的靜態成員變數和靜態成員方法 非靜態內部類不具有上述特徵
    2. 靜態內部類不能訪問外部類的非靜態成員和方法 非靜態內部類可以訪問外部類的非靜態成員和方法
    3. 靜態內部類在建立時不需要依賴外部類的例項 Outer.Inner inner = new Outer.Inner(); 非靜態內部類建立例項時需要依賴外部類例項 Outer.Inner inner = outer.new Outer.Inner();
  • 一個包內私有的方法(即方法的訪問許可權是default)不能被位於另一個包中的某個方法直接重寫 這點要注意,內容在解惑70
  • 靜態匯入
    1. 當靜態匯入的方法和本類中已有方法重名時,本類中方法具有優先權。識別到本地方法後就不會再使用匯入的方法了。
    2. 由此看來,靜態匯入也會引起一定的問題,要謹慎使用。
  • final
    1. 修飾成員方法,不能重寫
    2. 修飾靜態方法,不能被隱藏
    3. 修飾域,不管是靜態還是成員的,都只限制其值不能被賦值兩次,沒有其它限制
  • 如果兩個過載的方法能夠接收相同的引數,應該使得它們具有相同的行為。
  • 三目運算子
    1. 當第二個和第三個運算元為引用型別時,運算結果的型別是它們的最小公共超類
  • join()方法原理:在表示正在被連線的Thread例項上呼叫wait()方法,wait()方法會釋放執行緒鎖
  • 跨包訪問:
    1. 訪問位於其它包中的非公共型別的成員是不合法的。這些成員包括包、方法、域等。 舉例在公共類A中定義default的靜態內部類B,在包外的類C中執行A.B.hashCode()是非法的,因為B是defalut的,包外不可訪問。
    2. 這種非法問題,在正常時候編譯就能檢查出來,但是在反射時卻只有執行時才能看出效果。 因此,在使用反射訪問某個型別時,請保證使用的Class物件對應的型別是可被包外訪問的。
  • 一個非靜態的巢狀類的構造器,在編譯時會將一個隱藏的引數作為它的第一個引數買這個引數表示了它的直接外圍例項。 利用反射建立巢狀類物件時,就需要我們顯式地傳遞這個引數了。因此不能使用newInstance()來建立物件。取而代之的是用Constructor。
  • 靜態和非靜態內部類的選擇 內部類中需要使用直接外圍類時,建立非靜態內部類 其它情況優先建立靜態內部類
  • 應儘量避免使用反射來例項化內部類。
  • PrintStream
    1. System.out、System.err都是PrintStream型別的。
    2. PrintStream可被建立為自動重新整理的,觸發重新整理的條件:
      • 一個位元組陣列被寫入
      • println()方法被呼叫
      • 換行字元或者位元組被寫入
    3. 注意的是,write(int)方法是唯一一個在自動重新整理功能開啟的情況下不重新整理PrintStream的輸出方法
    4. 對應3.,System.out.write(int)方法就不會自動重新整理
  • 子程序
    1. 由於某些本地平臺只提供有限大小的緩衝,所以如果不能迅速地讀取子程序的輸出流,就有可能會導致子程序的阻塞,甚至死鎖。
    2. 解決方案:人為地排空子程序的輸出流,即獲取子process的輸入流,將流中的內容全部讀出。
  • 建立一個類的例項的方法
    1. 正常new
    2. 反射建立
    3. 序列化後反序列化
    4. 克隆
  • 對於一個實現了Serializable介面的單件類,必須有一個readResolve方法返回其唯一例項。否則,利用序列化反序列化可以創建出新的例項,從而失去單例 private Object readResolve() { return t; }
  • Thread.interrupt() : Thread.interrupted():測試當前執行緒是否中斷,同時清除當前執行緒的中斷狀態。 Thread.isInterrupted():測試當前執行緒是否終端,不會清除中斷狀態。
  • Thread的中斷機制
    1. Java的中斷是一種協作機制。也就是說呼叫執行緒物件的interrupt方法並不一定就中斷了正在執行的執行緒,它只是要求執行緒自己在合適的 時機中斷自己。每個執行緒都有一個boolean的中斷狀態(這個狀態不在Thread的屬性上),interrupt方法僅僅只是將該狀態置為true。
    2. 對正常執行的執行緒呼叫interrupt()並不能終止他,只是改變了interrupt標示符
    3. 如果一個方法宣告丟擲InterruptedException,表示該方法是可中斷的,比如wait,sleep,join,也就是說可中斷方法會對interrupt呼叫 做出響應
    4. Object.wait,Thread.sleep方法,會不斷的輪詢監聽interrupted標誌位,發現其設定為true後,會停止阻塞並丟擲 InterruptedException 異常。
  • Java在使用一個類時,總是會先檢查一下其是否已經進行了初始化。若沒有,則進行初始化,若發現正在初始化,則等待其初始化是否已經完成。
  • 將int或long轉換為float;long轉換為double時,都會導致精度丟失。要注意:使用
  • List<?> 萬用字元型別
    1. 與List相比,其實一個引數化型別,需要更強的型別檢查。
    2. 編譯器一般不會允許新增除了null以外的任何元素到List<?>中,而List可新增任意元素
    3. 應該避免直接使用原生型別
  • 原生型別、萬用字元型別、引數化型別是不同的,不能混用。