《Effective Java》學習筆記 —— 通用程式設計
本章主要討論區域性變數、控制結構、類庫、反射、本地方法的用法及程式碼優化和命名慣例。
第45條 將區域性變數的作用域最小化
* 在第一次使用的它的地方宣告區域性變數(就近原則)。
* 幾乎每個區域性變數的宣告都應該包含一個初始化表示式。如果還沒有足夠的資訊進行初始化,就延遲這個宣告(例外:try-catch語句塊)。
* 如果在迴圈終止之後不再需要迴圈變數的內容,for迴圈優先於while迴圈。
* 使方法小而集中(職責單一)。
第46條 for-each迴圈優先於傳統的for迴圈
* 如果正在編寫的型別表示的是一組元素,即使選擇不實現Collection,也要實現Iterable介面,以便使用for-each迴圈。
* for-each迴圈在簡潔性和預防Bug方面有著傳統for迴圈無法比擬的優勢,且沒有效能損失。但並不是所有的情況都能用for-each迴圈,如過濾、轉換和平行迭代等。
存在Bug的傳統for迴圈程式碼示例:
1 import java.util.*; 2 3 /** 4 * @author https://www.cnblogs.com/laishenghao/ 5 * @date 2018/10/7 6 */ 7 public class OrdinaryFor { 8 enum Suit { 9 CLUB, DIAMOND, HEART, SPADE,10 } 11 enum Rank { 12 ACE, DEUCE, THREE, FOUR, FIVE, 13 SIX, SEVEN, EIGHT, NINE, TEN, 14 JACK, QUEEN, KING, 15 } 16 17 public List<Card> createDeck() { 18 Collection<Suit> suits = Arrays.asList(Suit.values()); 19 Collection<Rank> ranks = Arrays.asList(Rank.values());20 21 List<Card> deck = new ArrayList<>(); 22 for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) { 23 for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) { 24 deck.add(new Card(i.next(), j.next())); 25 } 26 } 27 return deck; 28 } 29 30 31 static class Card { 32 final Suit suit; 33 final Rank rank; 34 35 public Card(Suit suit, Rank rank) { 36 this.suit = suit; 37 this.rank = rank; 38 } 39 40 // other codes 41 } 42 }
採用for-each迴圈的程式碼(忽略對Collection的優化):
1 public List<Card> createDeck() { 2 Suit[] suits = Suit.values(); 3 Rank[] ranks = Rank.values(); 4 5 List<Card> deck = new ArrayList<>(); 6 for (Suit suit : suits) { 7 for (Rank rank : ranks) { 8 deck.add(new Card(suit, rank)); 9 } 10 } 11 return deck; 12 }
第47條 瞭解和使用類庫
* 優先使用標準類庫,而不是重複造輪子。
第48條 如果需要精確的答案,請避免使用float和double
* float和double尤其不適合用於貨幣計算,因為要讓一個float或double精確的表示o.1(或10的任何其他負數次方值)是不可能的。
System.out.println(1 - 0.9);
上述程式碼輸出(JDK1.8):
* 使用BigDecimal(很慢)、int或者long進行貨幣計算。
第49條 基本型別優先於裝箱基本型別
* 在效能方面基本型別優於裝箱基本型別。當程式裝箱了基本型別值時,會導致高開銷和不必要的物件建立。
* Java1.5中增加了自動拆裝箱,但並沒有完全抹去基本型別和裝箱基本型別的區別,也沒有減少裝箱型別的風險。
如下程式碼在自動拆箱時會報NullPointerException:
Map<String, Integer> values = new HashMap<>(); int v = values.get("hello");
再考慮兩個例子:
例子1:輸出true
Integer num1 = 10;Integer num2 = 10;System.out.println(num1 == num2);
例子2:輸出false
Integer num1 = 1000; Integer num2 = 1000; System.out.println(num1 == num2);
為啥呢?
我們知道 “==” 比較的是記憶體地址。而Java預設對-128到127的Integer進行了快取(這個範圍可以在執行前通過-XX:AutoBoxCacheMax引數指定)。所以在此範圍內獲取的Integer例項,只要數值相同,返回的是同一個Object,自然是相等的;而在此範圍之外的則會重新new一個Integer,也就是不同的Object,記憶體地址是不一樣的。
具體可以檢視IntegerCache類:
1 /** 2 * Cache to support the object identity semantics of autoboxing for values between 3 * -128 and 127 (inclusive) as required by JLS. 4 * 5 * The cache is initialized on first usage. The size of the cache 6 * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. 7 * During VM initialization, java.lang.Integer.IntegerCache.high property 8 * may be set and saved in the private system properties in the 9 * sun.misc.VM class. 10 */ 11 12 private static class IntegerCache { 13 static final int low = -128; 14 static final int high; 15 static final Integer cache[]; 16 17 static { 18 // high value may be configured by property 19 int h = 127; 20 String integerCacheHighPropValue = 21 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); 22 if (integerCacheHighPropValue != null) { 23 try { 24 int i = parseInt(integerCacheHighPropValue); 25 i = Math.max(i, 127); 26 // Maximum array size is Integer.MAX_VALUE 27 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); 28 } catch( NumberFormatException nfe) { 29 // If the property cannot be parsed into an int, ignore it. 30 } 31 } 32 high = h; 33 34 cache = new Integer[(high - low) + 1]; 35 int j = low; 36 for(int k = 0; k < cache.length; k++) 37 cache[k] = new Integer(j++); 38 39 // range [-128, 127] must be interned (JLS7 5.1.7) 40 assert IntegerCache.high >= 127; 41 } 42 43 private IntegerCache() {} 44 }IntegerCache
第50條 如果其他型別更適合,則儘量避免使用字串
* 字串不適合代替其他的值型別。
* 字串不適合代替列舉型別。
* 字串不適合代替聚集型別(一個實體有多個元件)。
* 字串也不適合代替能力表(capacityies;capacity:能力,一個不可偽造的鍵被稱為能力)。
第51條 當心字串連線的效能
* 構造一個較小的、大小固定的物件,使用連線操作符(+)是非常合適的,但不適合運用在大規模的場景中。
* 如果數量巨大,為了獲得可以接受的效能,請使用StringBuilder(非同步),或StringBuffer(執行緒安全,效能較差,一般不需要用到)。
第52條 通過介面引用物件
* 這條應該與“面向介面程式設計”原則一致。
* 如果有合適的介面型別存在,則引數、返回值、變數和域,都應該使用介面來進行宣告。
如宣告一個類成員應當優先採用這種方法:
private Map<String, Object> map = new HashMap<>();
而不是:
private HashMap<String, Object> map = new HashMap<>();
* 如果沒有合適的介面存在,則完全可以採用類而不是介面。
* 優先採用基類(往往是抽象類)。
第53條 介面優先於反射機制
* 反射的代價:
(1)喪失了編譯時進行型別檢查的好處。
(2)執行反射訪問所需要的程式碼非常笨拙和冗長(編寫乏味,可讀性差)。
(3)效能差。
* 當然,對於某些情況下使用反射是合理的甚至是必須的。
第54條 謹慎地使用本地方法
* 本地方法(native method)主要有三種用途:
(1)提供“訪問特定於平臺的機制”的能力,如訪問登錄檔(registry)和檔案鎖(file lock)等。
(2)提供訪問遺留程式碼庫的能力,從而可以訪問遺留資料(legacy data)。
(3)編寫程式碼中注重效能的部分,提高系統性能(不值得提倡,JVM越來越快了)。
* 本地方法的缺點:
(1)不安全(C、C++等語言的不安全性)。
(2)本地語言與平臺相關,可能存在不可移植性。
(3)造成除錯困難。
(4)增加效能開銷。在進入和退出原生代碼時需要一定的開銷。如果本地方法只是做少量的工作,那就有可能反而會降低效能(這點與Java8的並行流操作類似)。
(5)可能會犧牲可讀性。
第55條 謹慎地進行優化
* 有三條與優化相關的格言是每個人都應該知道的:
(1)More computing sins are committed in the name of efficiency (without necessarily achieving it)than for any other single reason——including blind stupidity.
—— William AWulf
(2)We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
—— Donald E. Knuth
(3)We follow two rules in the matter of optimization:
Rule 1. Don't do it. Rule 2(for experts only). Don't do it yet——that is, not until you have a perfectly clear and unoptimized solution.
—— M. J. Jackson
以上格言說明:優化的弊大於利,特別是不成熟的優化。
* 不要因為效能而犧牲合理的結構。要努力編寫好的程式而不是快的程式。
實現上的問題可以通過後期優化,但遍佈全域性且限制性能的結構缺陷幾乎是不可能被改正的。但並不是說在完成程式之前就可以忽略效能問題。
* 努力避免那些限制性能的設計決策,考慮API設計決策的效能後果。
第56條 遵守普遍接受的命名慣例
* 把標準的命名慣例當作一種內在的機制來看待。