EffectiveJava-7-通用程序設計

2021-01-21 今陽說

將局部變量的作用域最小化

優點:增強代碼的可讀性和可維護性,並降低出錯的可能性;

最有力的方法就是在第一次使用它的地方聲明,幾乎每個局部變量的聲明都應該包含一個初始化的表達式, 如果還沒有足夠的信息進行初始化,就應該推遲這個聲明;

for(i,each)循環都支持聲明循環變量,所以當循環終止後不再需要循環變量的內容時,for循環就優先於while循環;

for-each循環優先於for-i循環

使用(集合)迭代器iterator或(數組)for-i的循環變量,由於循環變量會使用多次,且容易和外部的變量搞混,導致出錯;for-each則隱藏了循環變量,避免了混亂和出錯的可能;

性能優勢,對於數組索引的邊界值只計算一次。

對比下面代碼:

for-each不僅可以遍歷集合和數組,還可以遍歷任何實現Iterator接口的對象

簡潔性 ,預防bug,且沒有性能損失;

無法使用for-each情況:

1. 過濾:如果需要遍歷集合,並刪除選定的元素,就要使用顯示的迭代器,以便調用其remove方法 ;

2. 轉換:如果需要修改集合或數組的值,就需要集合的迭代器或數組索引了;

3. 平行迭代:如果需要並行的遍歷多個集合或數組,就需要用到迭代器或數組索引;

了解和使用類庫

看下面求隨機數方法:

這個方法表面看起來是沒有什麼問題的,然而,實際上卻有三個缺點:

1. 如果n是2的乘方,一段周期後,它生成的隨機數將重複;

2. 如果n不是2的乘方,則平均起來,有些數出現的更加頻繁,尤其n比較大時 ;

3. 極少數情況下會返回一個指定範圍外的數,如生成的隨機數為Integer.MIN_VALUE,Math.abs會返回負數MIN_VALUE,如下:

修正方案是使用Random的nextInt(int n)方法;

而如果要自己修正就需要了解偽隨機數生成器,數論和2的求補算法的相關知識;

使用類庫的優點:

1. 充分利用前人的知識和經驗;

2. 不必浪費時間處理與工作不太相關的底層細節;

3. 有專人或組織維護,性能隨著版本的迭代會不斷提高,bug也會被逐漸發現並修復,功能也會不斷豐富;

所以說不管是java還是Android,及時關注官方的api更新說明還是很有必要的,因為如果你不知道這些類庫或者新增加的功能, 可能會花費百倍千倍的時間去寫一些沒有必要,甚至可能有bug的的代碼,總之不要重複造輪子;

如果需要精確的答案,請避免使用float和double

float,double為科學計算和工程計算而設計,執行二進位浮點運算,然而它們並沒有提供精確的結果,尤其不適合用於貨幣計算;例如下面代碼:

修正方案:用BigDecimal代替double:

然而BigDecimal用起來比較麻煩,而且效率和性能要低(要創建BigDecimal對象) 另一種方法是使用int或long代替(根據具體數值大小決定),例如上面例子中的數值都*100;

BigDecimal還有一個優點就是可以完全控制捨入,並提供了8種捨入模式;

還有一點就是9位數以內用int,18位以內用long,超過18位就必須用BigDecimal了;

基本類型優先於裝箱基本類型

基本類型只有值,而裝箱基本類型不同的對象可以有相同值,相同值的可以是不同的對象;

基本類型只有功能值,裝箱基本類型還可以為null;

基本類型更節省時間和空間;

任何情況下,當一項操作中混合使用基本類型和裝箱基本類型時,裝箱基本類型就會自動拆箱 如Integer i;if(i1){...},會NullPointerException,正是由於拆箱時發現i==null;而且反覆的裝箱拆箱是會影響性能的,而且還可能導致一些你想不到的問題;

如果其他類型更合適,則儘量避免使用字符串

字符串不適合代替其他的值類型(基本數據類型 & 對象引用);

字符串不適合代替枚舉類型(如前面的第30條);

字符串不適合代替聚集類型(如果一個實體有多個組件,用一個字符串表示這個實體通常是很不恰當的);

字符串不適合代替能力表;

- 例如想設計一個線程局部變量ThreadLocal:

像上面這樣寫可以,但是又一個前提是key不能重複,如果多個線程(無意或故意的)用了相同的key,就會導致這個變量被多個線程共享,安全性很差;可以想下面這樣,各線程通過getKey獲取key,再以這個key進行變量的存取:

當然如果有興趣也可以看一些java.util.ThreadLocal的實現;

當心字符串連接的性能

我們使用字符串經常這樣寫"String name = "Mr."+"劉";",很方便,如果只是少量的使用確實可以;

但是:為連接n個字符串而重複的使用字符串連接操作符(+),需要n的平方級的時間。因為字符串是不可變的,+操作要創建新的對象,並拷貝要相加的兩個對象;

為了獲得可以接受的性能,請用StringBuilder替代String;相加次數越多,性能的差距是成平方級增長的;若有興趣可以寫個for循環列印時間戳試一下;

這一條其實我們剛入行時就都已經知道了,主要是平時開發過程中要注意應用;

通過接口引用對象

如果有合適的接口類型存在,那麼對於參數,返回值,變量和域來說,就都應該使用接口類型進行聲明;只有當用構造器創建某個對象時,才需要真正的引用這個對象的類;如: List list = new ArrayList();或 public void methodA(List list){...};

養成用接口作為類型的習慣,你的程序將更加靈活;(例如有新的實現可以提供更好的性能,可以方便的進行更換);

如果依賴域現實的任何特殊屬性,就要在聲明變量的地方給這些需求建立相應的文檔說明;

不適合的情況:

- 值類String,BigInteger等,一般是final的;

- 對象屬於給於類的框架,就應該使用基類(一般是抽象類),如java.util.TimerTask抽象類;

- 類提供類接口不存在的額外方法,切程序依賴於這些額外方法,如LinkedHashMap;

接口優先於反射機制

核心反射機制java.lang.reflect,提供了"通過程序來訪問關於已裝載的類的信息"的能力;如獲得Constructor,Method,Field;

然而也要付出相應的代價:

- (將編譯期錯誤推遲到了運行時)喪失了編譯時類型檢查的好處,包括異常檢查,且調用不存在或不可訪問的方法時,將在運行時失敗;

- 執行反射所需要的代碼非常笨拙和冗長,寫著乏味,讀著困難;

- 性能損失,調用反射方法比普通方法慢了許多;

反射功能通常只是在設計時被用到,普通的應用程式在運行時不應該以反射的方式訪問對象;

有些複雜的應用程式需要使用到反射機制,如瀏覽器,對象監視器,代碼分析工具,解釋型的內嵌式系統, 他們必須用到編譯時無法獲取的類,但是存在適當的接口或者超類,這種情況,可以通過反射創建實例, 通過接口或超類以正常的方式訪問這些實例,如果所用構造器不帶參數,可以考試使用Class.newInstance, 而非java.lang.reflect;

謹慎的使用本地方法

JNI(Java Native Interface)允許java程序調用本地方法(native method, 指本地程序語言c/c++編寫的方法);

native方法的三種用途:

- 提供"訪問特定於平臺的機制"的能力,如訪問註冊表,文件鎖 (隨著ava的發展已經在逐漸完善這些功能,如java,util.prefs提供了註冊表的功能,SystemTray提供訪問桌面系統託盤區的能力);

- 提供訪問遺留代碼庫的能力,從而可以訪問遺留數據;

- 通過本地語言編寫程序中注重性能的部分,提高程序性能(不值得提倡,隨著ava的發展性能已經被不斷的優化了);

native方法的缺點:

- 本地語言是不安全的,所以,使用本地方法的應用程式也不再能避免受內存毀壞錯誤的影響;

- 本地語言是平臺相關的,所以,使用本地方法的程序也不再是可自由移植的;

- 比較難調試;

- 進入和退出本地代碼時,需要一定的開銷,所以如果只是使用本地代碼做少量工作,反而可能降低性能;

- 需要"膠合代碼"的本地方法編寫起來單調乏味;

謹慎的進行優化

3條優化相關的格言:

- 很多計算上的過失都被歸咎於效率,而不是任何其他原因,甚至包括盲目的做傻事;

- 不要去計較效率上的一些小小的得失,在97%的情況下,不成熟的優化才是一切問題的根源

- 優化方面應該遵守兩條規則:

1. 不要優化;

2. (僅針對專家)還是不要進行優化;(可能你認為程序慢的地方並沒有問題;可以使用性能剖析工具檢測代碼)

總結:優化弊大於利,特別是不成熟的優化;

要努力編寫好的程序而不是快的程序,不要因為性能而犧牲合理的結構;

(再多底層的優化也無法彌補算法選擇的不當)

但必須在設計過程中考慮到性能問題:

- 避免那些限制性能的設計決策,尤其是API,線路層wire-level協議,永久數據格式)

- api設計對性能的影響:

1. 公有類型可變,會導致大量不必要的保護性拷貝;

2. 適合用複合模式時卻使用繼承,會導致與超類永遠束縛在一起,限制子類性能;

3. 使用實現類型代替接口,會把程序束縛在具體的實現上,即使將來有更優的實現也無法使用;

遵守普遍接受的命名慣例

包名:以組織的Internet域名開頭,且頂級域名放前面,如com.ljy; 且不應以java,javax開頭;

類和接口名:一個或多個單詞組成(通常是名詞或形容詞),每個單詞首字母大寫,儘量避免使用縮寫,尤其是你自己創造的縮寫;

方法和域的名稱:一個或多個單詞(方法通常用動詞,動詞短語或形容詞),除第一個單詞外首字母大寫;

常量域:唯一推薦使用下劃線的情形;

類型參數名稱:通常由單個字母組成,常用的一般由五種:T表示任意的類型,E表示集合的元素類型,K和V表示映射的鍵和值,X表示異常;(多個時可以這樣:T1,T2,T3...)

我是今陽,如果想要進階和了解更多的乾貨,歡迎關注公眾號」今陽說「接收我的最新文章

相關焦點

  • 計算機等級考試輔導:Java程序結構、類庫結構、程序開發環境結構
    J2ME技術是在1999年的JavaOne Developer Conference大會上正式推出,它將Java語言與平臺無關的技術特性移植到小型電子設備的應用開發中,允許移動無線設備之間共享應用程式。J2ME在手機端的應用開發目前已經被Andriod和IOS所取代,但在國內的廣電機頂盒、智慧卡等設備中仍然在應用。
  • 「Java」基礎06:編寫入門程序
    二、編譯.java文件是開發人員編寫的原始碼,java程序是在JVM上運行的,但是JVM並不認識.java文件。這個時候就必須要將源文件編譯成JVM能看懂的字節碼文件(.class文件)。在DOS命令行中,進入HelloWorld.java文件的目錄,使用java命令運行。
  • 使用eclipse開發Java程序詳解
    第一小節主要是使用eclipse創建HelloWorld項目,對eclipse有一個基本了解;第二小節主要介紹包的概念,包在Java程序開發中是一個很重要的概念;第三小節編寫求長方形面積算法的代碼,並運行程序。
  • Java基礎增強篇一,Java核心功能反射機制
    Java反射機制1. java代碼在 java 語言中最核心的就是代碼的運行, 按照面向對象的思想,在調用java代碼時往往需要先創建對象,再調用方法, 而寫在方法中的即所謂的java 代碼先分析Class對象的圖解:
  • Linux平臺下啟動java程序的腳本編寫
    畢竟java是跨平臺的嗎?可是,程序是跨平臺的,啟動腳本卻是和系統平臺緊密相關的。我就教教大家如何寫linux平臺下java的啟動腳本首先,我們登錄一個linux平臺這裡大家使用ssh客戶端遠程登錄系統。
  • Java窗體應用程式布局管理器
    import javax.swing.WindowConstants;/*** @ClassName: JFreamAbsolutelyTest* @Description: Swing程序設計代碼如下:importjava.awt.FlowLayout;* @ClassName: JFreamFlowLayoutTest* @Description: Swing程序設計(常用布局管理器)案例2
  • 程序小白入門JAVA,我該怎麼學?挑戰高薪就在今年
    Java是一種可以撰寫跨平臺應用程式的面向對象的程序設計語言。Java 技術具有卓越的通用性、高效性、平臺移植性和安全性,廣泛應用於PC、數據中心、遊戲控制臺、科學超級計算機、行動電話和網際網路,同時擁有全球最大的開發者專業社群。Java的分類java主要分三塊,j2se:java的基礎核心語言。
  • 如何應用觀察者設計模式重構系統中日誌處理功能實現的程序代碼
    讀者可以從作者的另一篇文章《軟體項目實訓及課程設計指導——如何應用策略設計模式的思想設計通用的資料庫連接類》一文中所示的黑體部分的代碼中能夠了解到常規的日誌記錄功能實現所存在的問題——直接將日誌記錄功能實現的代碼插入到資料庫連接ConnectDBBean類的功能實現代碼中,直接包含有日誌處理功能實現的程序代碼。
  • Java基礎篇——環境配置
    Java語言簡介Java語言源自於Oracle-Sun公司,是當今最通用、最流行的軟體開發語言之一。Java是簡單的、面向對象的語言,最大的特性是與平臺無關,有「write once, run everywhere」的稱號。
  • 零基礎學JAVA—Java簡介
    2.Java分為三個體系:JavaSE(J2SE) (Java2 Platform Standard Edition,java平臺標準版)JavaEE(J2EE) (Java 2 Platform,Enterprise Edition,java平臺企業版)
  • 給Java新手的一些建議——Java知識點歸納(Java基礎部分)
    Java的運行(基礎必備)這條可能出看很簡單,java程序的運行誰不會呢?不過很多時候, 我們只是單純通過IDE去執行java程序,底層IDE又是如何執行java程序呢?很多人並不了解。這個知識點是最最基本的java開發者需要掌握的,初學java,第一個肯定是教你如何在命令行中執行java程序,但是很多人一旦把java學完了,IDE用上了,就把這個都忘了。
  • 為什麼我們需要Java中的泛型類型?
    在編譯時查找錯誤可以節省調試Java程序的時間,因為編譯時錯誤更容易查找和修復。泛型類型僅在編譯時存在。這是學習Java泛型時要記住的最重要的事情。2.如果沒有泛型怎麼辦?在以下程序中,「 Room」類定義一個成員對象。我們可以將任何對象傳遞給它,例如String,Integer等。
  • java程序編寫的軟體
    它在編程中特別是在java語言中,處著重要的位置。對於Eclipse的下載、安裝也是非常簡單的,大家可以去Eclipse官網自行下載。下面就是Eclipse的圖標。它的裡面是英文的,雖然是英文大家也不用擔心,可以去搜索翻譯一下它的中文意思,但是英語好對編程非常有利的。大家接下來好好積累一下單詞吧!
  • 自考《Java語言程序設計(一)》串講筆記(4)
    利用Visual Cafe,用戶可以從一個標準對象資料庫中集合完整的Java應用程式和Applet,而不必再編寫原始碼。Visual Cafe還提供了一個擴充的原始碼開發工具集。 Visual Cafe綜合了Java軟體的可視化源程序開發工具,它允許開發人員在可視化視圖和源視圖之間進行有效地轉換。在可視化視圖中進行的修改立即反映在原始碼中。
  • 跟我學java編程—Java的標準輸出
    用記事本打開「PrintSample.java」文件,輸入以下代碼:代碼結構分析:程序主要功能是調用System.out.print方法,完成不同類型數據的輸出,查看輸出結果。編譯「PrintSample.java」文件,在命令行窗口輸入「javac PrintSample.java」並執行命令,編譯通過後,在命令行窗口輸入「java PrintSample.java」運行Java程序,命令行窗口顯示如下信息:
  • Java資料庫連接性簡介
    JDBC是您的應用程式代碼與之交互的通用API。下面是您正在使用的資料庫的JDBC兼容驅動程序。圖1是Java持久層中JDBC的體系結構概述。使用JDBC連接資料庫Java生態系統中編程的幸運事實之一是,您可能會為所選的任何資料庫找到穩定的JDBC資料庫連接器。在本教程中,我們將使用SQLite來了解JDBC,主要是因為它非常易於使用。
  • 跟我學java編程—認識java的整數類型
    ① 八進位整型常量:在八進位數值前面加前綴數字0,其數碼取值為0—7,例如:023、0457、01329等;② 十六進位整型常量:前綴為「0X」或「0x」,數碼取值0—9、A—F、或a—f。用記事本打開「OverFlow.java」文件,輸入以下代碼:編譯「OverFlow.java」文件,在命令行窗口輸入「javac OverFlow.java」並執行命令,編譯器顯示如下信息:編譯器給出過大的整數錯誤信息,num
  • java是什麼文件格式?.java文件怎麼打開?
    Java是由Sun Microsystems公司於1995年5月推出的Java面向對象程序設計語言和Java平臺的總稱,是當今最流行的編程技術。  java是什麼文件?  Java文件是由Sun Microsystems公司於1995年5月推出的Java程序設計語言和Java平臺的總稱。
  • Java程序是如何運行的
    JVM是Java的運行時虛擬機,所有的Java程序都是在JVM沙箱中運行,每個Java程序就是一個獨立的JVM進程。談到Java程序是如何運行的,首先需要理解的肯定是JVM是如何運行的,什麼是JVM;要理解我們編寫的Java程序,運行起來以後到底是什麼樣子,本質上就是弄清楚JVM是什麼樣子。
  • 新手學Java編程應該學那些Java基礎知識
    Java的運行(基礎必備)  這條可能出看很簡單,java程序的運行誰不會呢?不過很多時候, 我們只是單純通過IDE去執行java程序,底層IDE又是如何執行java程序呢?很多人並不了解。  這個知識點是最最基本的java開發者需要掌握的,初學java,第一個肯定是教你如何在命令行中執行java程序,但是很多人一旦把java學完了,IDE用上了,就把這個都忘了。