通用程序設(shè)計

該篇文章是《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)該熟悉以下三種類庫:

  1. java.lang
  2. jang.util ( java.util.concurrent 很重要哦)
  3. 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); //常用,推薦使用

其中,

  1. double 參數(shù)的構(gòu)造方法,不允許使用!!!!因為它不能精確的得到相應(yīng)的值;
  2. String 構(gòu)造方法是完全可預(yù)知的: 寫入 new BigDecimal("0.1") 將創(chuàng)建一個 BigDecimal,它正好等于預(yù)期的0.1; 因此,通常建議優(yōu)先使用 String 構(gòu)造方法;
  3. 靜態(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市隔箍,隨后出現(xiàn)的幾起案子谓娃,更是在濱河造成了極大的恐慌,老刑警劉巖蜒滩,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滨达,死亡現(xiàn)場離奇詭異,居然都是意外死亡俯艰,警方通過查閱死者的電腦和手機捡遍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竹握,“玉大人画株,你說我怎么就攤上這事。” “怎么了蜈项?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長良拼。 經(jīng)常有香客問我战得,道長,這世上最難降的妖魔是什么庸推? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任常侦,我火速辦了婚禮,結(jié)果婚禮上贬媒,老公的妹妹穿的比我還像新娘聋亡。我一直安慰自己,他們只是感情好际乘,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布坡倔。 她就那樣靜靜地躺著,像睡著了一般脖含。 火紅的嫁衣襯著肌膚如雪罪塔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天养葵,我揣著相機與錄音征堪,去河邊找鬼。 笑死关拒,一個胖子當(dāng)著我的面吹牛佃蚜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播着绊,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼谐算,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了归露?” 一聲冷哼從身側(cè)響起洲脂,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剧包,沒想到半個月后腮考,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡玄捕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了棚放。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枚粘。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖飘蚯,靈堂內(nèi)的尸體忽然破棺而出馍迄,到底是詐尸還是另有隱情福也,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布攀圈,位于F島的核電站暴凑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赘来。R本人自食惡果不足惜现喳,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望犬辰。 院中可真熱鬧嗦篱,春花似錦、人聲如沸幌缝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涵卵。三九已至浴栽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轿偎,已是汗流浹背典鸡。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贴硫,地道東北人椿每。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像英遭,于是被迫代替她去往敵國和親间护。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容