人非圣賢,,孰能無過,。都說Java語言是一門簡單的編程語言,基于C++演化而來,,剔除了很多C++中的復(fù)雜特性,,但這并不能保證Java程序員不會犯錯。那么對于廣大的Java程序員來說,,它們最常犯的10個錯誤是什么呢,?本文通過總結(jié)出Java程序員最常犯的10大錯誤,可以有效地幫組Java后來者少走彎路,,少加班,,并寫出更健壯的應(yīng)用程序。1. 數(shù)組轉(zhuǎn)ArrayList 為了實現(xiàn)把一個數(shù)組轉(zhuǎn)換成一個ArrayList,,很多Java程序員會使用如下的代碼: Arrays.asList確實會返回一個ArrayList對象,,但是該類是Arrays類 中一個私有靜態(tài)內(nèi)部類,而不是常見的java.util.ArrayList類,。這個java.util.Arrays.ArrayList類具有 set,,get,,contains等方法,但是不具有任何添加或移除元素的任何方法,。因為該類的大小(size)是固定的,。為了創(chuàng)建出一個真正的ArrayList,代碼應(yīng)該如下所示: 我們知道,,ArrayList的構(gòu)造方法可以接受一個Collection類型的對象,,而我們的 java.util.Arrays.ArrayList正好也是它的一個子類。實際上,,更加高效的代碼示例是: 2. 數(shù)組是否包含特定值 為了檢查數(shù)組中是否包含某個特定值,,很多Java程序員會使用如下的代碼: 就功能而言,該代碼是正確無誤的,,但在數(shù)組轉(zhuǎn)List,,List再轉(zhuǎn)Set的過程中消耗了大量的性能。我們可以優(yōu)化成如下形式: 或者,,進(jìn)一步優(yōu)化成如下所示最高效的代碼: 3. 在迭代時移除List中的元素 首先,,看一下在迭代過程中移除List中元素的代碼: 這個示例代碼的輸出結(jié)果是: 這個示例代碼中存在一個非常嚴(yán)重的錯誤。當(dāng)一個元素被移除時,,該List的大小(size)就會縮減,,同時也改變了索引的指向。所以,,在迭代的過程中使用索引,,將無法從List中正確地刪除多個指定的元素。 你可能知道解決這個錯誤的方式之一是使用迭代器(iterator),。而且,,你可能認(rèn)為Java中的foreach語句與迭代器(iterator)是非常相似的,但實際情況并不是這樣,。我們考慮一下如下的示例代碼: 這個示例代碼會拋出來一個ConcurrentModificationException,。我們應(yīng)該修改成如下所示: next方法必須在remove方法之前被調(diào)用。在 foreach循環(huán)中,,編譯器使得 remove方法先于next方法被調(diào)用,,這就導(dǎo)致了ConcurrentModificationException 異常。具體細(xì)節(jié)可以查看ArrayList.iterator的源碼,。 4. Hashtable vs HashMap學(xué)習(xí)過數(shù)據(jù)結(jié)構(gòu)的讀者都知道一種非常重要的數(shù)據(jù)結(jié)構(gòu)叫做哈希表,。在Java中,對應(yīng)哈希表的的類是HashMap而不是Hashtable,。HashMap與Hashtable之間的最核心區(qū)別就是:HashMap是非同步的,,Hashtable是同步的。 5. 在Collection中使用原始類型在Java中,,很容易把原始類型與無限通配類型混淆,。我們舉個Set相關(guān)的例子:Set就是原始類型,;Set?>就是無限通配類型。我們看一個使用在List中使用原始類型的例子: 這個示例代碼會拋出來一個異常: 在Collection使用原始類型是具有很多的類型錯誤風(fēng)險的,,因為原始類型沒有靜態(tài)類型檢查,。實際上,Set,、Set?>和Set之間具有非常大的差異,。 6. 訪問權(quán)限很多的Java初學(xué)者喜歡使用public來修飾類的成員。這樣可以很方便地直接訪問和存取該成員,。但是,,這是一種非常糟糕的編程風(fēng)格,正確的設(shè)計風(fēng)格應(yīng)該是盡可能降低類成員的訪問權(quán)限,。 7. ArrayList vs LinkedList很多的Java初學(xué)者不明白ArrayList與LinkedList之間的區(qū)別,,所以,他們完全只用相對簡單的ArrayList,,甚至不知道JDK中還存在LinkedList,。但是,在某些具體場景下,,這兩種List的選擇會導(dǎo)致程序性能的巨大差異,。簡單而言:當(dāng)應(yīng)用場景中有很多的add/remove操作,只有少量的隨機(jī)訪問操作時,,應(yīng)該選擇LinkedList;在其他的場景下,,考慮使用ArrayList。 8. 可變 vs 不可變不可變的對象具有非常多的優(yōu)勢,,比如簡單,,安全等。但是,,對于每一個不同的值,,都需要該類的一個對象。而且,,生成很多對象帶來的問題就是可能導(dǎo)致頻繁的垃圾回收。所以,,在選擇可變類還是不可變類時,,應(yīng)該綜合考慮后再做抉擇。 通常而言,,可變對象可以避免創(chuàng)建大量的中間對象,。一個非常經(jīng)典的例子就是鏈接大量的短String對象為一個長的String對象。如果使用不可變String類,,鏈接的過程將產(chǎn)生大量的,,適合立即被垃圾回收的中間String對象,,這將消耗大量的CPU性能和內(nèi)存空間。此時,,使用一個可變的StringBuilder或StringBuffer才是正確的,。 除了上述情況,可變對象在其他場景下可能用于不可變對象,。比如,,傳遞一個可變的對象到方法內(nèi)部,利用該對象可以收集多個結(jié)果,,而不用在多個循環(huán)層次中跳進(jìn)跳出,。 9. 繼承中的構(gòu)造函數(shù)上圖中出現(xiàn)的兩個編譯時錯誤是因為:父類中沒有定義默認(rèn)構(gòu)造函數(shù),而子類中又調(diào)用了父類的默認(rèn)構(gòu)造函數(shù),。在Java中,,如果一個類不定義任何構(gòu)造函數(shù),編譯期將自動插入一個默認(rèn)構(gòu)造函數(shù)到給類中,。一旦一個類定義了任何一個構(gòu)造函數(shù),,編譯期就不會插入任何構(gòu)造函數(shù)到類中。在上面的示例中,,Super類定義了一個參數(shù)類型為String的構(gòu)造函數(shù),,所以該類中只有一個構(gòu)造函數(shù),不會有默認(rèn)構(gòu)造函數(shù)了,。 &emps;在我們的子類 Sub 中,,我們定義了兩個構(gòu)造函數(shù):一個參數(shù)類型為String的構(gòu)造函數(shù),另一個為午餐的默認(rèn)函數(shù),。由于它們都沒有在函數(shù)體的第一行指定調(diào)用父類的哪一個構(gòu)造函數(shù),,所以它們都需要調(diào)用父類 Super 的默認(rèn)構(gòu)造函數(shù)。但是,,父類 Super 的默認(rèn)構(gòu)造函數(shù)是不存在的,,所以編譯器報告了這兩個錯誤信息。 10. 字符串對象的兩個構(gòu)建方式Java中的字符串對象具有兩個常見的創(chuàng)建方式: 它們之間的區(qū)別是什么呢,?我們再看一下如下的代碼: |
|