人非圣賢轿钠,孰能無過雹熬。都說Java語言是一門簡單的編程語言,基于C++演化而來谣膳,剔除了很多C++中的復雜特性竿报,但這并不能保證Java程序員不會犯錯。那么對于廣大的Java程序員來說继谚,它們最常犯的10個錯誤是什么呢烈菌?本文通過總結(jié)出Java程序員最常犯的10大錯誤,可以有效地幫組Java后來者少走彎路,少加班芽世,并寫出更健壯的應用程序挚赊。
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,代碼應該如下所示:
我們知道柬帕,ArrayList的構(gòu)造方法可以接受一個Collection類型的對象哟忍,而我們的 java.util.Arrays.ArrayList正好也是它的一個子類。實際上陷寝,更加高效的代碼示例是:
在這里還是要推薦下我自己建的Java學習裙:574加上二五三再加上075锅很,群里都是學Java開發(fā)的,如果你正在學習Java 凤跑,小編歡迎你加入爆安,大家都是軟件開發(fā)黨,不定期分享干貨(只有Java軟件開發(fā)相關(guān)的)饶火,包括我自己整理的一份2018最新的Java進階資料和高級開發(fā)教程鹏控,歡迎進階中和進想深入java的小伙伴
2. 數(shù)組是否包含特定值
為了檢查數(shù)組中是否包含某個特定值致扯,很多Java程序員會使用如下的代碼:
就功能而言肤寝,該代碼是正確無誤的抖僵,但在數(shù)組轉(zhuǎn)List鲤看,List再轉(zhuǎn)Set的過程中消耗了大量的性能。我們可以優(yōu)化成如下形式:
或者耍群,進一步優(yōu)化成如下所示最高效的代碼:
3. 在迭代時移除List中的元素
首先义桂,看一下在迭代過程中移除List中元素的代碼:
這個示例代碼的輸出結(jié)果是:
這個示例代碼中存在一個非常嚴重的錯誤。當一個元素被移除時蹈垢,該List的大小(size)就會縮減慷吊,同時也改變了索引的指向。所以曹抬,在迭代的過程中使用索引溉瓶,將無法從List中正確地刪除多個指定的元素。
你可能知道解決這個錯誤的方式之一是使用迭代器(iterator)。而且堰酿,你可能認為Java中的foreach語句與迭代器(iterator)是非常相似的疾宏,但實際情況并不是這樣。我們考慮一下如下的示例代碼:
這個示例代碼會拋出來一個ConcurrentModificationException触创。我們應該修改成如下所示:
next()方法必須在remove()方法之前被調(diào)用坎藐。在 foreach循環(huán)中,編譯器使得 remove()方法先于next()方法被調(diào)用哼绑,這就導致了ConcurrentModificationException 異常岩馍。具體細節(jié)可以查看ArrayList.iterator()的源碼拆讯。
4. Hashtable vs HashMap
學習過數(shù)據(jù)結(jié)構(gòu)的讀者都知道一種非常重要的數(shù)據(jù)結(jié)構(gòu)叫做哈希表芯杀。在Java中拔妥,對應哈希表的的類是HashMap而不是Hashtable柱嫌。HashMap與Hashtable之間的最核心區(qū)別就是:
HashMap是非同步的瘦赫,Hashtable是同步的甸鸟。
5. 在Collection中使用原始類型
在Java中第队,很容易把原始類型與無限通配類型混淆谦疾。我們舉個Set相關(guān)的例子:Set就是原始類型励稳;Set就是無限通配類型佃乘。我們看一個使用在List中使用原始類型的例子:
這個示例代碼會拋出來一個異常:
在Collection使用原始類型是具有很多的類型錯誤風險的,因為原始類型沒有靜態(tài)類型檢查驹尼。實際上趣避,Set、Set和Set之間具有非常大的差異新翎。
6. 訪問權(quán)限
很多的Java初學者喜歡使用public來修飾類的成員程帕。這樣可以很方便地直接訪問和存取該成員。但是地啰,這是一種非常糟糕的編程風格愁拭,正確的設(shè)計風格應該是盡可能降低類成員的訪問權(quán)限。
7. ArrayList vs LinkedList
很多的Java初學者不明白ArrayList與LinkedList之間的區(qū)別亏吝,所以岭埠,他們完全只用相對簡單的ArrayList,甚至不知道JDK中還存在LinkedList蔚鸥。但是惜论,在某些具體場景下,這兩種List的選擇會導致程序性能的巨大差異止喷。簡單而言:當應用場景中有很多的add/remove操作馆类,只有少量的隨機訪問操作時,應該選擇LinkedList;在其他的場景下弹谁,考慮使用ArrayList乾巧。
8. 可變 vs 不可變
不可變的對象具有非常多的優(yōu)勢技羔,比如簡單,安全等卧抗。但是藤滥,對于每一個不同的值,都需要該類的一個對象社裆。而且拙绊,生成很多對象帶來的問題就是可能導致頻繁的垃圾回收。所以泳秀,在選擇可變類還是不可變類時标沪,應該綜合考慮后再做抉擇。
通常而言嗜傅,可變對象可以避免創(chuàng)建大量的中間對象金句。一個非常經(jīng)典的例子就是鏈接大量的短String對象為一個長的String對象。如果使用不可變String類吕嘀,鏈接的過程將產(chǎn)生大量的违寞,適合立即被垃圾回收的中間String對象,這將消耗大量的CPU性能和內(nèi)存空間偶房。此時趁曼,使用一個可變的StringBuilder或StringBuffer才是正確的。
除了上述情況棕洋,可變對象在其他場景下可能用于不可變對象挡闰。比如,傳遞一個可變的對象到方法內(nèi)部掰盘,利用該對象可以收集多個結(jié)果摄悯,而不用在多個循環(huán)層次中跳進跳出。
9. 繼承中的構(gòu)造函數(shù)
上圖中出現(xiàn)的兩個編譯時錯誤是因為:父類中沒有定義默認構(gòu)造函數(shù)愧捕,而子類中又調(diào)用了父類的默認構(gòu)造函數(shù)奢驯。在Java中,如果一個類不定義任何構(gòu)造函數(shù)晃财,編譯期將自動插入一個默認構(gòu)造函數(shù)到給類中叨橱。一旦一個類定義了任何一個構(gòu)造函數(shù)典蜕,編譯期就不會插入任何構(gòu)造函數(shù)到類中断盛。在上面的示例中,Super類定義了一個參數(shù)類型為String的構(gòu)造函數(shù)愉舔,所以該類中只有一個構(gòu)造函數(shù)钢猛,不會有默認構(gòu)造函數(shù)了。
&emps;在我們的子類 Sub 中轩缤,我們定義了兩個構(gòu)造函數(shù):一個參數(shù)類型為String的構(gòu)造函數(shù)命迈,另一個為午餐的默認函數(shù)贩绕。由于它們都沒有在函數(shù)體的第一行指定調(diào)用父類的哪一個構(gòu)造函數(shù),所以它們都需要調(diào)用父類 Super 的默認構(gòu)造函數(shù)壶愤。但是淑倾,父類 Super 的默認構(gòu)造函數(shù)是不存在的,所以編譯器報告了這兩個錯誤信息征椒。
10. 字符串對象的兩個構(gòu)建方式
Java中的字符串對象具有兩個常見的創(chuàng)建方式:
它們之間的區(qū)別是什么呢娇哆?我們再看一下如下的代碼: