本章主要討論語言的具體內(nèi)容蠕搜。它討論了局部變量的處理、控制結(jié)構(gòu)轨蛤、庫的使用虫埂、各種數(shù)據(jù)類型的使用,以及使用反射和本地方法缝呕。最后斧散,討論了優(yōu)化和命名約定
Item 45:最小化局部變量作用域
作用域:一個花括號{}包裹起來的區(qū)域
此條例同Item13相似:最小化類和成員變量的訪問權(quán)限
Java允許你在任何地方聲明變量,但是最重要的是在首次使用的地方聲明變量,并初始化
循環(huán)提供了一種實(shí)現(xiàn)此種方式的機(jī)制,而且for循環(huán)比while循環(huán)好,如
for (Element e : c) {
doSomething(e);
}
//before JDK1.5
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomething((Element) i.next());
}
為什么for比while好呢?
//bad
Iterator<Element> i = c.iterator();
while (i.hasNext()) {
doSomething(i.next());
}
...
Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) { // BUG! 應(yīng)該是i2
doSomethingElse(i2.next());
}
當(dāng)我們寫一個差不多的代碼,從一個地方copy過來的時(shí)候,很有可能忘記修改某個變量值(如i2),它不會在編譯期報(bào)錯,我們很可能長時(shí)間遺留這個bug
使用for循環(huán)可以避免這個bug
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
doSomething(i.next());
}
...
// Compile-time error - cannot find symbol i
for (Iterator<Element> i2 = c2.iterator(); i.hasNext(); ) {
doSomething(i2.next());
}
這就是最小化作用域的好處
Item 46: Prefer for-each loops to traditional for loops
對于不需要下標(biāo)來做特殊操作的遍歷,推薦使用增強(qiáng)for循環(huán)
你能發(fā)現(xiàn)下面的bug嗎?
enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
NINE, TEN, JACK, QUEEN, KING }
...
Collection<Suit> suits = Arrays.asList(Suit.values());
Collection<Rank> ranks = Arrays.asList(Rank.values());
List<Card> deck = new ArrayList<Card>();
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); )
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
deck.add(new Card(i.next(), j.next()));
...
...
...
...
...
...
發(fā)現(xiàn)不了也不要難過,很多有經(jīng)驗(yàn)的程序員也會犯這個錯誤
原因在于i.next()會被重復(fù)調(diào)用,導(dǎo)致結(jié)果異常
可以修復(fù)如下
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ){
Suit suit = i.next();
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
deck.add(new Card(suit, j.next()));
}
雖然解決了問題,但是很丑,更簡潔的寫法如下
for (Suit suit : suits)
for (Rank rank : ranks)
deck.add(new Card(suit, rank));
盡可能的使用增強(qiáng)for循環(huán)
Item 47: Know and use the libraries
不要重復(fù)造輪子
如果需要一個常用的功能,去發(fā)現(xiàn)一個庫并使用它,往往比自己寫的要好
因?yàn)閹鞎S著時(shí)間推移更新迭代越來越好,自己也節(jié)約了時(shí)間
這并不會評論一個人的能力
Item 48: Avoid float and double if exact answers are required
當(dāng)需要一個精確的答案時(shí),避免使用float或double類型的變量
float或double是為了科學(xué)和工程計(jì)算而設(shè)計(jì)的,特別不適用于貨幣計(jì)算
推薦使用int或long代替
Item 49: Prefer primitive types to boxed primitives
使用原始類型替代裝箱類型
byte short char int long float double boolbean等是Java中的基本類型,也有對應(yīng)的裝箱類型,如 Integer, Double, and Boolean.應(yīng)當(dāng)謹(jǐn)慎對待這兩者之間的差別
首先第一個差別,原始類型僅僅包含對應(yīng)的值,裝箱類型既包含對應(yīng)的值也有對應(yīng)的引用,第二個差別是裝箱類型有可能為null,第三個差別是原始類型在時(shí)間和空間消耗中更高效
Item 50: Avoid strings where other types are more appropriate
如果有更合適的類型,避免使用String
string被設(shè)計(jì)成描述文本類型的數(shù)據(jù),而且干得很好,本章主要討論將string用于其它情況的錯誤用法
string不能替代值類型 如果我們正在等待鍵盤的輸入,或者從網(wǎng)絡(luò)獲取某個值,我們很方便的使用string作為接收類型,但是如果我們輸入的是數(shù)字或者真假值的話,對應(yīng)的int或boolean能更好的標(biāo)識輸入 雖然這條規(guī)則很明顯,但是經(jīng)常被違反
string不能替代枚舉 如Item30:枚舉討論的那樣,對于靜態(tài)常量使用枚舉
string不能替代聚合字符
String compoundKey = className + "#" + i.next();//bad
我們經(jīng)常寫上述代碼,這樣的寫法有很多缺點(diǎn).如果我們想使用某一部分字段需要解析字符串,很耗時(shí)而且容易出錯.String提供的equals,compareTo等方法也不能使用
比較好的方式是寫一個靜態(tài)內(nèi)部類來表示聚合字符
static class CompoundKey{
private String className;
private String next;
...
}
Item 51: Beware the performance of string concatenation
考慮字符連接(+)的性能
使用(+)能很方便的拼接若干個字符串,但是我們也要考慮到開銷
如下兩個代碼都是拼接字符
//方式一
public String statement() {
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i); // String concatenation
return result;
}
//方式二
public String statement() {
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
for (int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
}
當(dāng)numItems()==100;lineForItem(i)返回80個長度的字符時(shí),在作者的機(jī)器上方式二比方式一快85倍
如果我們需要拼接大量字符時(shí),使用StringBuilder代替
Item 52: Refer to objects by their interfaces
使用接口代替對象引用
如果有一個合適的接口來描述當(dāng)前類的時(shí)候使用這個接口來引用,如
// Good - uses interface as type
List<Subscriber> subscribers = new Vector<Subscriber>();
// Bad - uses class as type!
Vector<Subscriber> subscribers = new Vector<Subscriber>();
如果你養(yǎng)成了這個習(xí)慣,那么你的代碼將會更靈活
有幾種情況不能使用接口
1.沒有合適的接口聲明
2.接口中沒有想要的某個方法
3.使用的類集成自framework,并且是一個抽象類
Item 53: Prefer interfaces to reflection
使用接口代替反射
反射核心類java.lang.reflect提供了對加載類信息的訪問
例如源祈,Method.Invoke允許在任何類的任何對象上調(diào)用任何方法(受通常的安全約束)色迂。 即使編譯后的類不存在,也允許一個類使用另一個類赫悄。然而,這種力量是有代價(jià)的姑隅。
你不能在編譯時(shí)檢查出異常 如果你使用了對應(yīng)的反射操作,在發(fā)生異常時(shí),只有在運(yùn)行時(shí)才能發(fā)現(xiàn)
閱讀性極差
性能有影響 使用反射比普通調(diào)用要慢些,因?yàn)槭芎芏嘁蛩氐挠绊?慢多少很難說,在作者的機(jī)器上,速度差在兩倍到五十倍不止
反射核心用在基于組件設(shè)計(jì)的應(yīng)用,為了按需加載類,使用反射找到對應(yīng)的類構(gòu)造與否;普通應(yīng)用盡量不要使用反射,找到代替的接口或者父類對象
Item 54: Use native methods judiciously
明智地使用本地方法
JNI允許Java調(diào)用C或C++寫的本地方法
從歷史上看讲仰,本地方法有三種主要用途痪蝇。
1.它們提供了對特定于平臺的設(shè)施的訪問,例如注冊表和文件鎖趁矾。
2.他們提供了對舊代碼庫(Java想使用歷史上C或C++寫的庫)
的訪問给僵,可以反過來提供對舊數(shù)據(jù)的訪問。
3.使用本地方法用本地語言編寫應(yīng)用程序關(guān)鍵部分蔓同,以提高性能蹲诀。
Java平臺在不斷發(fā)展,訪問特定平臺設(shè)施,使用Java提供的工具類就可做到,而且也不建議使用本地方法來提升程序性能
使用本地方法有嚴(yán)重的缺點(diǎn)
1.本地語言是不安全的,會受機(jī)器內(nèi)存錯誤的影響
2.依賴于平臺,不便于移植
3.本地代碼很難調(diào)式
4.訪問本地代碼開銷很大
5.本地代碼不宜閱讀
總之,要再三思考是否使用本地代碼,如果需要使用以前的代碼庫,請盡可能減少本地代碼片段并加強(qiáng)測試,很小很小的本地代碼錯誤將破壞你的整個程序
Item 55: Optimize judiciously
明智的優(yōu)化
有三個人人都應(yīng)該知道的優(yōu)化格言
More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason—including blind stupidity.
—William A. Wulf [Wulf72]
更多的計(jì)算罪惡是以效率的名義犯下的(不一定要達(dá)到這一目的)脯爪,而不是因?yàn)槿魏纹渌麊我坏脑?包括盲目的愚蠢
?
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
—Donald E. Knuth [Knuth74]
我們應(yīng)該忽略小效率,大約百分之97的情況下,邪惡之源就是過早優(yōu)化
?
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. A. Jackson [Jackson75]
關(guān)于優(yōu)化遵循如下兩個原則:
原則1.別這樣做
原則2.(僅對專家而言).還不要做,直到你找到一個清晰的解決方案或者一個未優(yōu)化的解決辦法
不要為了性能而損壞架構(gòu),致力于寫出好的程序而不是快的程序,如果一個好的程序還不夠快,它的架構(gòu)會允許優(yōu)化.
這并不意味著當(dāng)你的程序完后不需要優(yōu)化,你應(yīng)該在設(shè)計(jì)階段就考慮到性能
盡量避免影響性能的設(shè)計(jì),已經(jīng)實(shí)現(xiàn)好的組件很難在改變,尤其是API,數(shù)據(jù)結(jié)構(gòu),多方約定好的協(xié)議
考慮好API使用效果,設(shè)計(jì)一個可變的類可能會導(dǎo)致后續(xù)使用中出現(xiàn)過多的深拷貝,造成對象分配的額外開銷
API的設(shè)計(jì)對性能有很真實(shí)的影響,如java.awt.Component中的getSize()方法,每調(diào)用一次就會返回一個新的 Dimension 實(shí)例(JDK1.2版本已經(jīng)修復(fù)),雖然分配一個實(shí)例的開銷很小,但是成百上千次調(diào)用也會對程序有嚴(yán)重影響
幸運(yùn)的是,好的API設(shè)計(jì)自然帶來了好的性能
總之,致力于寫出好的程序,快隨之而來 當(dāng)做出一部分改變后就要測量代碼的性能,對于Android來說內(nèi)存,卡頓,anr等方面
?
Item 56: Adhere to generally accepted naming conventions
遵循公共的命名規(guī)范,參考阿里巴巴發(fā)布的Android手冊