Java開發人員比較常犯的10個錯誤
有錯誤要避免,看看Java程序員比較常犯的十個錯誤,以后開發的時候不要再犯錯了哦。
一、把數組轉成ArrayList
為了將數組轉換為ArrayList,開發者經常會這樣做:
List
使用Arrays.asList()
方法可以得到一個ArrayList,但是得到這個ArrayList
其實是定義在Arrays類中的一個私有的靜態內部類。這個類雖然和java.util.ArrayList
同名,但是并不是同一個類。java.util.Arrays.ArrayList
類中實現了set()
,get()
,contains()
等方法,但是并沒有定義向其中增加元素的方法。也就是說通過Arrays.asList()
得到的ArrayList的大小是固定的。
如果在開發過程中,想得到一個真正的ArrayList對象(java.util.ArrayList
的實例),可以通過以下方式:
ArrayList
java.util.ArrayList
中包含一個可以接受集合類型參數的構造函數。因為java.util.Arrays.ArrayList
這個內部類繼承了AbstractList類,所以,該類也是Collection的子類。
二、判斷一個數組是否包含某個值
在判斷一個數組中是否包含某個值的時候,開發者經常這樣做:
Set
在在Java中如何高效的判斷數組中是否包含某個元素
一文中,深入分析過,以上方式雖然可以實現功能,但是效率卻比較低。因為將數組壓入Collection類型中,首先要將數組元素遍歷一遍,然后再使用集合類做其他操作。
在判斷一個數組是否包含某個值的時候,推薦使用for循環遍歷的形式或者使用ApacheCommons類庫中提供的ArrayUtils
類的contains
方法。
三、在循環中刪除列表中的元素
在討論這個問題之前,先考慮以下代碼的輸出結果:
ArrayList
輸出結果:
[b,d]
以上代碼的目的是想遍歷刪除list中所有元素,但是結果卻沒有成功。原因是忽略了一個關鍵的問題:當一個元素被刪除時,列表的大小縮小并且下標也會隨之變化,所以當你想要在一個循環中用下標刪除多個元素的時候,它并不會正常的生效。
也有些人知道以上代碼的問題就由于數組下標變換引起的。所以,他們想到使用增強for循環的形式:
ArrayList
但是,很不幸的是,以上代碼會拋出ConcurrentModificationException
,有趣的是,如果在remove操作后增加一個break,代碼就不會報錯:
ArrayList
在Java中的fail-fast機制
一文中,深入分析了幾種在遍歷數組的同時刪除其中元素的方法以及各種方法存在的問題。其中就介紹了上面的代碼出錯的原因。
迭代器(Iterator)是工作在一個獨立的線程中,并且擁有一個mutex鎖。迭代器被創建之后會建立一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,所以當索引指針往后移動的時候就找不到要迭代的對象,所以按照fail-fast原則迭代器會馬上拋出java.util.ConcurrentModificationException
異常。
所以,正確的在遍歷過程中刪除元素的方法應該是使用Iterator:
ArrayList
next()
方法必須在調用remove()
方法之前調用。如果在循環過程中先調用remove()
,再調用next()
,就會導致異常ConcurrentModificationException
。原因如上。
四、HashTable和HashMap的選擇
了解算法的人可能對HashTable比較熟悉,因為他是一個數據結構的名字。但在Java里邊,用HashMap來表示這樣的數據結構。Hashtable和HashMap的一個關鍵性的不同是,HashTable是同步的,而HashMap不是。所以通常不需要HashTable,HashMap用的更多。
HashMap完全解讀
、Java中常見親屬比較等文章中介紹了他們的區別和如何選擇。
五、使用原始集合類型
在Java里邊,原始類型和無界通配符類型很容易混合在一起。以Set為例,Set是一個原始類型,而Set是一個無界通配符類型。(可以把原始類型理解為沒有使用泛型約束的類型)
考慮下面使用原始類型List作為參數的代碼:
publicstaticvoidadd(Listlist,Objecto){list.add(o);}publicstaticvoidmain(String[]args){List
上面的代碼將會拋出異常:
java.lang.ClassCastException:java.lang.Integercannotbecasttojava.lang.String
使用原始集合類型是很危險的,因為原始集合類型跳過了泛型類型檢查,是不安全的。Set、Set和Set
六、訪問級別
程序員們經常使用public
作為類中的字段的修飾符,因為這樣可以很簡單的通過引用得到值,但這并不是好的設計,按照經驗,分配給成員變量的訪問級別應該盡可能的低。參考Java中的四種訪問級別
七、ArrayList
與LinkedList
的選擇
當程序員們不知道ArrayList
與LinkedList
的區別時,他們經常使用ArrayList,因為它看起來比較熟悉。然而,它們之前有巨大的性能差別。在ArrayListvsLinkedListvsVector區別
、Java中常見親屬比較等文章中介紹過,簡而言之,如果有大量的增加刪除操作并且沒有很多的隨機訪問元素的操作,應該首先LinkedList
。(LinkedList
更適合從中間插入或者刪除(鏈表的特性))
八、可變與不可變
在為什么Java要把字符串設計成不可變的
一文中介紹過,不可變對象有許多的優點,比如簡單,安全等等。同時,也有人提出疑問:既然不可變有這么多好處,為什么不把所有類都搞成不可變的呢?
通常情況下,可變對象可以用來避免產生過多的中間對象。一個經典的實例就是連接大量的字符串,如果使用不可變的字符串,將會產生大量的需要進行垃圾回收的對象。這會浪費CPU大量的時間,使用可變對象才是正確的方案(比如StringBuilder
)。
Stringresult="";for(Strings:arr){result=result+s;}
StackOverflow
中也有關于這個的討論。
九、父類和子類的構造函數
上圖的代碼中有兩處編譯時錯誤,原因其實很簡單,主要和構造函數有關。首先,我們都知道:
如果一個類沒有定義構造函數,編譯器將會插入一個無參數的默認構造函數。
如果一個類中定義了一個帶參數的構造函數,那么編譯器就不會再幫我們創建無參的構造函數。
上面的Super類中定義了一個帶參數的構造函數。編譯器將不會插入默認的無參數構造函數。
我們還應該知道:
子類的所有構造函數(無論是有參還是無參)在執行時,都會調用父類的無參構造函數。
所以,編譯器試圖調用Super類中的無參構造函數。但是父類默認的構造函數未定義,編譯器就會報出這個錯誤信息。
要解決這個問題,可以簡單的通過
1)在父類中添加一個Super()構造方法,就像這樣:publicSuper(){}
2)移除自定義的父類構造函數
3)在子類的構造函數中調用父類的super(value)。
十、””還是構造函數
關于這個問題,也是程序員經常出現困惑的地方,在該如何創建字符串,使用”“還是構造函數?
中也介紹過.
如果你只需要創建一個字符串,你可以使用雙引號的方式,如果你需要在堆中創建一個新的對象,你可以選擇構造函數的方式。
在Stringd=newString("abcd")
時,因為字面值“abcd”已經是字符串類型,那么使用構造函數方式只會創建一個額外沒有用處的對象。
更多資訊盡在動力節點官方微信《動力節點Java學院》,更多好看好玩新聞盡在微信平臺,不定時的還有小活動哦。