該篇文章是《Effective Java 2nd》的讀書總結(jié),關(guān)于此書自然不必多言笔宿。如果有需要pdf中文&英文的,可以下方留言护锤。據(jù)說現(xiàn)在已經(jīng)有了第三版啦,點擊查看:Effective Java 第三版的相關(guān)介紹
第8章 通用程序設(shè)計
——以下內(nèi)容都是來自于該章節(jié)的總結(jié)
1. 將局部變量的作用域最小化
在第一次使用它的地方聲明,幾乎每個局部變量的聲明都應(yīng)該直接初始化
(如果沒有足夠的信息來對這個局部變量進行有意義的初始化缔俄,那么就應(yīng)該推遲這個聲明,直到可以初始化為止器躏。 有例外情況俐载,具體視情況而定)
如果在循環(huán)終止之后不再需要變量的內(nèi)容,我們選擇for循環(huán)就優(yōu)先于while循環(huán)登失。
// 我們使用Iterator遍歷java集合
首選的做法:
for(Iterator i = c.iterator(); i.hasNext() ){
doSomething((Element)i.next());
}
另外一種對局部變量的作用域進行最小化的循環(huán)做法:
// 此處的countLength()表示的是可能耗時需要計算的操作遏佣;
// 這樣寫可以避免每次迭代中執(zhí)行冗余計算的開銷
for(int i = 0 , length = countLength() ; i < lenght ; i ++){
doSomething(i);
}
對于局部變量,晚聲明(初始化),早結(jié)束,即不可擴大局部變量的作用域
在實際開發(fā)中揽浙,對于該條規(guī)則肯定是深有體會的状婶。尤其是我們擅長的Ctrl+C & Ctrl +V,異常就離程序不遠了意敛。
2. for-each循環(huán)優(yōu)先于傳統(tǒng)的for循環(huán)
前提條件——我們不需要借助于index來執(zhí)行操作
for - each循環(huán)適用于數(shù)組&集合及任何實現(xiàn)Iterator接口的對象,且無毒副作用(無性能損失),IAW,使用它百利而無一害膛虫。這一點在多個集合進行嵌套迭代時草姻,它的優(yōu)勢將會更加明顯。
/**
* 假設(shè)2個骰子,有多少種組合
*/
enum Face {ONE,TWO,THREE,FOUR,FIVE,SiX}
Collection<Face> faces = Arrays.asList(Face.values());
//使用傳統(tǒng)的for循環(huán)
for(Iterator<Face> i = faces.iterator(); i.hasNext();){
for(Iterator<Face> j = faces.iterator(); j.hasNext();){
Sysout.out.println(i.next() +" , "+j.next());
// 結(jié)果輸出的是:6個重復(fù)的單詞("ONE,ONE" 到 "SIX,SIX"),而不是我們期望的36種
}
}
為了fix 上面的bug,必須在外部循環(huán)的作用添加一個變量來保存外部元素,即:
// 此處只貼出關(guān)鍵代碼
for (Iterator<Face> i = faces.iterator(); i.hasNext(); ) {
Face nextI = i.next();
for (Iterator<Face> j = faces.iterator(); j.hasNext(); ) {
System.out.println(nextI + "," + j.next());
}
}
按照傳統(tǒng)的for循環(huán)來做上述簡單功能,我們難道還需如履薄冰嗎稍刀?NO
// for- each循環(huán)撩独,想你之所想
for (Face face : faces) {
for (Face f : faces) {
System.out.println(face+","+f);
}
}
IAW,for-each循環(huán)在簡潔性&預(yù)防Bug方面有著傳統(tǒng)for循環(huán)無可比擬的優(yōu)勢,并且沒有性能損失账月,So開發(fā)中综膀,盡可能地使用for-each循環(huán)。
補充一句局齿,如果牽涉到Index,我們就必須使用傳統(tǒng)的for循環(huán)啦剧劝。
3. 強烈建議使用標(biāo)準(zhǔn)類庫
“不要重復(fù)的造輪子”,能用標(biāo)準(zhǔn)庫實現(xiàn)那就別猶豫抓歼,因為我們不一樣讥此。性能,代碼風(fēng)格都有保障锭部。
在java中暂论,每個程序員都應(yīng)該熟悉以下三種類庫:
- java.lang
- jang.util ( java.util.concurrent 很重要哦)
- java.io
其他的類庫根據(jù)需要進行學(xué)習(xí)。
4. 如果需要精準(zhǔn)的答案拌禾,那就扔掉Float & Double
強行裝13一波,可以直接略過
float 和 double 類型主要是為了科學(xué)計算和工程計算而設(shè)計的。它們執(zhí)行二進制浮點運算展哭,這是為了在廣泛的數(shù)值范圍上提供較為精準(zhǔn)的快速近似計算而精心設(shè)計的湃窍。然而它們并沒有提供完全精確的結(jié)果,所以不應(yīng)該被用于需要精準(zhǔn)結(jié)果的場合匪傍。
// WTF,這咋不是我想要的呢您市?
System.out.println(1.03 - 0.42);//0.6100000000000001
System.out.println(1.0 - 0.9);//0.09999999999999998
System.out.println(1.0 - 0.2);// 0.8
System.out.println(1.0-0.1-0.2-0.3-0.4); // -5.551115123125783E-17
如何解決上述這種問題呢?
正確姿勢是:使用BigDecimal 或者想辦法轉(zhuǎn)化為int,long來計算役衡;
// 使用 BigDecimal計算
public static void main(String[] args){
// 其實BigDecimal構(gòu)造函數(shù)有很多茵休,注意這里的BigDecimal的構(gòu)造函數(shù)里面是字符串
BigDecimal bigDecimal = new BigDecimal("1.03");
BigDecimal bigDecimal1 = new BigDecimal("0.42");
BigDecimal result = bigDecimal.subtract(bigDecimal1);
System.out.println("1.03 - 0.42 = "+result); // 1.03 - 0.42 = 0.61 perfect
BigDecimal bigDecimal2 = BigDecimal.valueOf(1.0);
BigDecimal bigDecimal3 = BigDecimal.valueOf(0.9);
BigDecimal subtract = bigDecimal2.subtract(bigDecimal3);
System.out.println("1.0 - 0.9 = "+ subtract); // 1.0 - 0.9 = 0.1 nice
// 想辦法湊成int 或者long再做
System.out.println((1.03 * 100 - 0.42 * 100)/100); // 0.61
}
構(gòu)造 BigDecimal 對象常用以下方法:
BigDecimal BigDecimal(double d); //不允許使用,得不到精確值
BigDecimal BigDecimal(String s); //常用,推薦使用
static BigDecimal valueOf(double d); //常用,推薦使用
其中,
- double 參數(shù)的構(gòu)造方法,不允許使用!!!!因為它不能精確的得到相應(yīng)的值;
- String 構(gòu)造方法是完全可預(yù)知的: 寫入 new BigDecimal("0.1") 將創(chuàng)建一個 BigDecimal,它正好等于預(yù)期的0.1; 因此,通常建議優(yōu)先使用 String 構(gòu)造方法;
- 靜態(tài)方法 valueOf(double val) 內(nèi)部實現(xiàn),仍是將 double 類型轉(zhuǎn)為 String 類型; 這通常是將 double(或float)轉(zhuǎn)化為 BigDecimal 的首選方法;
BigDecimal詳情,值得你收藏。
這里也要注意手蝎,BigDecaimal也是有缺點的:
你自己寫也能感覺出來 1. 不夠方便榕莺;2. 慢,影響程序性能低
根據(jù)實際情況取舍棵介。
如果性能非常關(guān)鍵钉鸯,你又不介意十進制的小數(shù)點,且數(shù)值不大邮辽,就可以考慮使用int 或者long.
如果數(shù)值范圍沒有超過9位十進制數(shù)值唠雕,就可以使用int贸营;
如果數(shù)值范圍沒有超過18位十進制數(shù)值,就可以使用long;
否則岩睁,必須使用BigDecimal.
5.基本類型優(yōu)先于裝箱基本類型
Java的類型系統(tǒng)由兩部分組成:
1.基本類型(primitive); byte ,char, int ,long,float ,double , boolean
2.引用類型(reference); String, List...and so on.
裝箱基本類型(boxed primitive)就是 基本類型對應(yīng)的引用類型钞脂。
1.Byte
2.Character
3.Integer
4.Long
5.Float
6.Double
7.Boolean
Java 1.5版本引入了自動裝箱(autoboxing)和自動拆箱(auto-unboxing)。 該特性模糊了但沒有完全抹去基本類型和裝箱基本類型之間的區(qū)別捕儒。我們在開發(fā)中要謹(jǐn)慎選擇芳肌。
基本類型和裝箱基本類型的區(qū)別:
1.基本類型只有值,而裝箱基本類型則具有與它們的值不同的同一性(IOW,兩個裝箱基本類型可以具體相同的值和不同的同一性)[這里的同一性: 其實就是對象的內(nèi)存地址]肋层;
2.每個裝箱基本類型都有一個值:null亿笤;
3.基本類型通常比裝箱基本類型更節(jié)省空間和時間,雖然裝箱基本類型也進行了部分的性能優(yōu)化(可以查看相關(guān)類的valueOf()方法)栋猖;
// 裝箱基本類型
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1 == i2? " + (i1 == i2)); // false
System.out.println("i1.equals(i2)? " + (i1.equals(i2))); // true;
// 裝箱基本類型
Integer j1 = 1; //自動裝箱
Integer j2 = 1; //自動裝箱
System.out.println("j1 == j2? " + (j1 == j2)); // true;
System.out.println("j1.equals(j2)? " + (j1.equals(j2))); // true
再看個更神奇的栗子
Integer i11 = 127;
Integer i22 = 127;
System.out.println("i11 == i11? " + (i11 == i22)); // true
System.out.println("i11.equals(i22)? " + (Objects.equals(i11, i22)));// true
Integer i33 = 128;
Integer i44 = 128;
/**
* 在通過valueOf方法創(chuàng)建Integer對象的時候净薛,如果數(shù)值在[-128,127]之間,
* 便返回指向IntegerCache.cache中已經(jīng)存在的對象的引用蒲拉;否則創(chuàng)建一個
* 新的Integer對象肃拜。
*/
System.out.println("i33 == i44? " + (i33 == i44));// false
System.out.println("i33.equals(i44)? " + (Objects.equals(i33, i44)));// true
Double d1 = 200.0;
Double d2 = 200.0;
System.out.println("d1 == d2? "+(d1 == d2)); // true
System.out.println("d1.equals(d2)? " + (Objects.equals(d1, d2))); // true
無形裝B,最為致命。
以后看見裝箱基本類型雌团,比較的時候就直接用equals()就安全啦燃领。實在不行,就裝個Alibaba的插件
注意:
重要的事情說3遍:
1.裝箱基本類型比較相等時用equals();
2.裝箱基本類型比較相等時用equals();
3.裝箱基本類型比較相等時用equals();
裝箱基本類型在開發(fā)中還容易引起以下錯誤锦援,這里順便提一下:
public class TestAutoBox{
public static Integer num;
public static void main(){
// NullPointerException
/**
* 錯誤原因:
* 當(dāng)使用基本類型和裝箱基本類型進行操作時猛蔽,一般裝箱基本類型都會自動拆
* 箱。但是類似于這種情況灵寺,此時裝箱基本類型為null時曼库,就得到這個
* NullPointerException異常。
*/
if(num == 42){
System.out.println("You are so lucky as to Unbelievable");
}
}
}
另一種情況:
public class TestBox2{
// 裝箱基本類型
private Long sum = 0L;
public static void main(){
for(long i = 0; i < Integer.MAX_VALUE; i++){
/**
* 程序編譯運行沒有問題略板,但是由于sum是裝箱基本類型毁枯,
* 所以 += 操作將進行反復(fù)地裝箱和拆箱,導(dǎo)致明顯的性能下降
*/
sum += i叮称;
}
System.out.println(sum);
}
}
到這里种玛,你可能有疑惑,So Why 發(fā)明這個裝箱基本類型呢瓤檐?存在即合理在某些場景下赂韵,我們不得不用裝箱基本類型。
使用裝箱基本類型的場景:
1.使用集合(Collections Framework)中距帅;
2.在參數(shù)化類型中右锨;
3.反射的方法調(diào)用中;
總結(jié)
1.包裝基本類型套路太多碌秸,盡量避免使用绍移;
2.包裝類型進行操作時悄窃,請尊重人家對象的本質(zhì),按對象的規(guī)格接待它(equals(),null)蹂窖;
3.使用基本類型安全可靠轧抗;
另外,關(guān)于裝箱基本類型的詳細內(nèi)容瞬测,
可以參考深入理解Java中的包裝類與自動拆裝箱
6.選擇合適的類型横媚,放字符串一條生路
這條規(guī)則其實是一些人在開發(fā)中為了方便而養(yǎng)成的壞習(xí)慣,坦白地講月趟,以前團隊就有成員這么干灯蝴,我也想不通為什么。
7.注意字符串連接的性能
String 字符串常量
StringBuffer 字符串變量(線程安全)
StringBuilder 字符串變量(非線程安全)
StringBuilder類是Java5.0新增加的孝宗,用于代替在非同步情況下的StringBuffer類穷躁。
String 類型和 StringBuffer(或者StringBuilder)類型的主要性能區(qū)別在于String是不可變對象。所以在每次對String類型進行改變的時候因妇,其實就等同于生成了一個新的String對象问潭,然后將指針指向新的String對象。到此體驗一把
在字符串連接的使用中婚被,大部分情況下狡忙,性能上:
- StringBuffer > String;
- StringBuilder > StringBuffer;
需要注意的是:
// 這樣的拼接是沒有任何問題的,因為在JVM'Eyes,它
// 等價于 String string = "Hello,Good Morning Sir";
String string = "Hello" +" Good" + "Morning" +"Sir";
總而言之址芯,經(jīng)常改變內(nèi)容的字符串最好不要用String灾茁,在非同步的情況下,直接選擇StringBuilder是复。因為每次生成對象都會系統(tǒng)性能產(chǎn)生影響删顶。
String,StringBuffer與StringBuilder的區(qū)別
Difference between StringBuilder and StringBuffer
另外,身邊的同事轉(zhuǎn)化字符串時淑廊,總是直接(+"")來搞,令我十分反感特咆。在這里懇求大家季惩,在開發(fā)中,一定要優(yōu)雅地來處理String.valueOf(param):
int i = 10;
String s1 = i + "";// 不推薦腻格,產(chǎn)生兩個對象
String s = String.valueOf(i);//推薦
8.通過接口引用對象
一般來講画拾,應(yīng)該優(yōu)先使用接口而非類來引用對象。如果有合適的接口類型存在菜职,那么對于參數(shù)青抛,返回值,變量和域來講酬核,都應(yīng)該使用接口類型進行聲明蜜另。
ArrayList<Integer> arrayList = new ArrayList<Integer>(10);//不推薦
正確姿勢:
List<Integer> list = new ArrayList<Integer>(10);// 推薦
Don't ask me WHY? 你想想适室,那天發(fā)現(xiàn)ArrayList不滿足需求,要換成LinkedList举瑰。采用第一種寫法捣辆,你會不會Crazy.
如果頂層不是接口,使用基類也是可以的啊
9.謹(jǐn)慎地進行優(yōu)化
關(guān)于優(yōu)化的格言:
1.很多計算上的過失都被歸咎于效率(沒有必要達到的效率)此迅,而不是其他的任何原因----甚至包括盲目地做傻事汽畴。
2.不要去計較效率上的一些小損失,在97%的情況下耸序,不成熟的優(yōu)化才是一切問題的根源忍些。
3.在優(yōu)化方面,我們應(yīng)該遵守兩條規(guī)則:
*規(guī)則1:不要進行優(yōu)化坎怪;
*規(guī)則2:(僅針對專家):還是不要優(yōu)化——That's to say,在你還沒有絕對清晰的未優(yōu)化方案前罢坝,請不要優(yōu)化。
要努力編寫好的程序而不是快的程序
過早地優(yōu)化乃萬惡之源
善用性能剖析工具來分析代碼芋忿。
IOW炸客,不要費力去編寫快的程序——應(yīng)該努力編寫好的程序,速度自然會隨之而來戈钢。
10.遵守大眾的命名規(guī)范
這里只強調(diào)一點痹仙,
類型參數(shù):
T:表示任意的類型;
E:表示集合的元素殉了;
K&V:表示映射的鍵和值的類型开仰;
X:表示異常。
任何類型可以是T,U,V或者T1,T2,T3薪铜。
我之前的CTO曾對我說過一句話众弓,我感覺很受用:
你的代碼能讓后來看的人不罵你就足夠啦
這里分享一個不錯的在線編輯網(wǎng)站:GeeksforGeeks