45.將局部變量的作用域最小化
簡介
和
13
條,使類和成員的可訪問性最小化,是一個道理,可以採取如下幾種辦法:
- 在第一次使用它的地方聲明.
- 幾乎每個局部變量的聲明都應該包含一個初始化的表達式,否則(沒有足夠的信息來對一個變量進行有意義的初始化),就應該延遲這個聲明.
- for循環優於while循環,而且防止了“剪切-粘貼”錯誤,且更加簡短可讀
- 最後一種方法是
將局部變量的作用域最小化的方法是使方法小而集中
46.for-each優於for循環
簡介
利用
fro-each
循環,對數組的索引邊界值只會計算一次.
並且 看起來很 簡潔下面是一個示例:
for(Iterator<Suit> i = suits.iterator();i.hasNext()){
for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
deck.add(i.next(),j.next());
}
}
這段代碼,看似沒問題,其實在內層循環中,會多次調用
i.next()
,有可能拋出NoSuchElementException
,正確的用法應該是;
for(Iterator<Suit> i = suits.iterator();i.hasNext()){
Suit suit = i.next();
for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
deck.add(suit,j.next());
}
}
而用
for-each
,就會變成這樣
for(Suit suit:suits){
for(Rank rank: ranks){
deck.add(suit,rank);
}
}
小結
建議如果 編寫的類型表示的是一組元素, 應該讓它實現 Iterable
不能用for-each 的場景
在需要獲取 數組或者迭代器索引
的時候.
47.瞭解和使用類庫
不正確實例
產生位於
0
和某個上界
之間的隨機整數.常見代碼,如下:
private static final Random rnd = new Random();
static int random(int n){
return Math.abs(rnd.nexInt())%n;
}
這個方法有三個缺點:
- 如果n 是一個比較小的 2 的盛放,經過一段週期,隨機數序列會重複
- 如果n 不是2 的平方,平均起來,有些數比其他數出現的更爲頻繁
- 極少數情況下,會失敗
幸運的是,在Java標註庫
中已經有了相關實現,nextInt(int)
,因此可以充分利用這些標準類庫.
小結
總而言之,不要重新發明輪子,如果要做的事情十分常見,有肯能標準類庫中某個類已經完成了這樣的工作.
48.如果需要精確的答案,避免使用float和double
簡介
float 和 double 是爲了提供較爲精確的 快速近似計算而精心設計的.
不適合用於貨幣計算
解決辦法:
- 使用
BigDecimal
,與使用基本運算類型相比,BigDecimal
不是很方便,而且很慢. - 除了使用
BigDecimal
,還用一種辦法是使用int
或者long
,者取決於所涉及的數據的大小
小結
- 精確計算避免使用
float
和double
,要使用BigDecimal
,這樣可以控制舍入, - 如果不介意自己記錄十進制小數點,可以使用
int
或者long
- 如果數值超過 18位數字,則必須使用
BigDecimal
49.基本類型優先於裝箱基本類型
簡介
Java
類型系統包含 基本類型
和 引用類型
,每一種基本類型
都有一個對應的引用類型
,稱爲裝箱基本類型
,Java1.5 版本提供了自動裝箱
和自動拆箱
基本類型與裝箱類型的區別
- 基本類型只有值,而裝箱基本類型則具有
與他們的值不同的同一性
- 基本類型只有功能完備的值,而裝箱類型除了功能值之外,還有非功能值
null
- 基本類型通常比裝箱基本類型更
節省時間和空間
.
警醒
- 對
裝箱基本類型
運用==
操作符 幾乎總是錯誤的
.
Comparator<Integer> naturalOrder = new Comparator<Integer>() {
public int compare(Integer first, Integer second) {
return first < second ? -1 : (first == second ? 0 : 1);
}
};
naturalOrder.compare(new Integer(42),new Integer(42));//返回1
- 基本類型和裝箱類型混合使用,會自動拆箱,而
null
的拆箱操作會拋出NullPointException
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42)//拋出異常
System.out.println("Unbelievable");
}
}
- 包裝類型,會創建過多的對象,造成性能問題
//這是之前第5個item中的代碼
public static void main(String[] args) {
Long sum = 0L;//這裏每次都會構造一個實例,多創建了2^31個實例.
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum); }
小結
- 基本類型優先於 裝箱類型,基本類型更加簡單快速
- 使用裝箱類型,要考慮其自動拆箱的問題
- 警惕
==
50.如果其他類型更適合,則儘量避免使用字符串
簡介
字符串 常被用來表示文本 .因爲其通用性,就出現瞭如下一些不適合的場景
字符串不適合代替其他的值類型.
- 當一段數據從網絡,文件或者輸入設備進入到程序之後,就應該轉化爲其本來的類型.而不是繼續使用String
- 字符串不適合代替枚舉類型(
枚舉類型比字符串更加適合用來表示枚舉常量
) - 字符串不適合代替聚集類型(
拼接字符串場景
) - 字符串不適合代替能力表(
ThreadLocal
的例子)
小結
- 如果可以使用更加合適
的數據類型,就 應該避免使用
字符串`來表示 - 字符串如果使用不當,比其他類型更加笨拙,速度慢.
51.當心字符串連接的性能
簡介
字符串連接符 是將
多個字符串
合併爲一個字符串
的遍歷途徑.
爲連接 n 個字符串而重複地使用連接符,需要n 的平方級的時間
(這是由於字符串不可變導致的)
爲了獲得可以接受的性能,請使用StringBuilder
考慮如下兩種情況:
public String statement() {
String result = "";
for (int i = 0; i < numItems(); ++i) {
result += lineForItem(i);
}
return result;
}
public String statement() {
StringBuilder stringBuilder = new StringBuilder(numItems * LINE_WIDTH);
for (int i = 0; i < numItems(); ++i) {
stringBuilder.append(lineForItem(i));
}
return stringBuilder.toString();
}
後者
比 前者
快 85
倍之多.
原因:
第一種隨項目數量而呈平方增加,第二種則是線性增加
第二種,默認分配一個足夠容納結果的StringBuilder
,即使使用默認大小,也比第一種快50
倍之多.
52.通過接口引用對象
簡介
第
40 條
中,建議使用接口
作爲參數類型
其實,應該是 優先使用接口
,而不是類
來引用對象.
如果有合適的接口類型存在,對於參數
,返回值
,變量
,域
都應該使用接口類型
來聲明.
示例
// 使用接口引用指向子類對象,good
List<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
// 子類引用指向子類對象,bad
Vector<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
//使用接口的好處之一,就是可以方便的更換實現類
List<SubscriptSpan> mSubscriptSpen = new ArrayList<SubscriptSpan>();
- 如果沒有合適的接口存在,完全可以用類而不是接口來引用對象.
不存在相應接口的情況:
- 值類(很少會有多個實現),
final
類 - 對象屬於一個框架,而框架的基本類型是類.
- 類實現了接口,但是提供了接口中不存在的額外方法.
53.接口優先於反射機制
簡介
反射提供了一種能力: 通過程序來訪問已裝載的類的信息的能力,即使被編譯的時候後者還不存在,然而,
反射的代價:
喪失了編譯時類型檢查
的好處(包括異常檢查). 如果程序企圖 用反射調用不存在或者不可訪問的方法,將會運行失敗.- 執行反射訪問所需要的
代碼
非常笨拙和冗長
: 編寫乏味,閱讀也困難 - 性能損失: 反射的 執行比普通方法調用要
慢一些.
反射的場景
- 反射通常只在
設計時被用到
,普通應用在運行時不應該以反射方式
訪問對象. - 以反射的形式創建實例,然後通過它們的接口或者超類,已正常方式訪問這些實例.
小結
反射機制是一種功能強大的機制,但也有一些缺點.
如果編寫的程序必須要與編譯時 未知的類 一起工作,如 有可能,應該僅僅使用反射機制實例化對象
,訪問對象
時則使用 編譯時 已知的接口或者超類
54.謹慎的使用本地方法
簡介
Java Native Interface(JNI)
允許 java
程序調用 本地方法
.
本地方法主要有 三種用途:
- 提供了
訪問特定於平臺
的機制 - 訪問老的
C/C++
版本的庫,訪問遺留代碼的能力 - 通過本地方法,編寫應用中注重性能的部分
JNI的劣勢
- 不安全,內存管理不受
JVM
控制了 - 與平臺相關,自由移植變得困難
- 難以調試
Java和native
層的交互是有開銷的,如果本地代碼只做少量工作,則會降低性能
小結
總而言之,使用本地代碼要三思,
- 極少數情況下需要使用本地方法來提高性能.
- 如果必須要使用,也要儘可能全面的測試.
55.謹慎的進行優化
簡介
很多計算上的過失都被歸咎於效率(沒有必要達到的效率),而不是任何其他的原因-- 甚至包括盲目的做啥事
---
不要去計較效率上的一些小小得失,在 `97%`的情況下,不成熟的優化纔是一切問題的根源
---
在優化方面,應該遵循兩條規則
規則1 : 不要進行 優化.
規則2 (僅針對專家) : 還是不要進行優化 -- 也就是說,在你還沒有絕對清晰的未優化方案之前,請不要進行優化.
這些格言,比Java 出現早了
20
年,他們講述了優化的深刻真理:
1. 優化的弊大於利,特別是不成熟的優化
2. 要努力表寫好的程序,而不是快的程序
3. 努力避免那些限制性能的設計決策
4. 要考慮`API`設計決策的性能後果
5. 爲了獲得好的性能對`API`進行包裝是一個不好的想法
6. 在每次試圖做優化之前和之後,豆芽對性能進行測量.
小結
總而言之.,不要費力去編寫快速的程序 – 應該努力編寫好的程序,速度自然會隨之而來. 當 構建完系統
後,要測量
它的性能
.
56.遵守普遍的命名規則
簡介
Java 平臺建立了一 整套很好的命名慣例.分爲兩大類:
字面的
和語法的.
- 包名要體現出組件的層次結構,全小寫
- 公佈到外部的,包名以公司/組織的域名開頭,如 com.xxx.xxx
- 執行某個動作的方法長用動詞或者動詞短語來命名,如 append
- 轉換對象類型的方法,
小結
把命名規則當做一種內在的機制來看待,並且學者用他們作爲第二特徵.