Java程式設計師常犯的10個錯誤

programcreek發表於2014-06-18

  本文總結了Java程式設計師常犯的10個錯誤。

  #1. 把Array轉化成ArrayList

  把Array轉化成ArrayList,程式設計師經常用以下方法:

List<String> list = Arrays.asList(arr);

  Arrays.asList() 實際上返回一個ArrayList,但是這個ArrayList是Arrays的一個內部私有類,而不是java.util.ArrayList類。這個私有類java.util.Arrays.ArrayList有set(), get(), contains()方法,但是不能夠新增新的元素。它的大小是固定的。如果你想要一個java.util.ArrayList,正確的方法是:

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));

  java.util.ArrayList的建構函式可以接受一個集合型別。java.util.Arrays.ArrayList也繼承了集合型別,所以可以作用引數使用。

  #2. 檢查陣列是否包含一個值

  開發人員經常做的是:

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);

  這個程式碼是工作的,但沒有沒有效率。把列表轉換成set沒有必要,需要額外的時間。正確的方法是:

Arrays.asList(arr).contains(targetValue);

  或者,一個簡單的loop:

for(String s: arr){
	if(s.equals(targetValue))
		return true;
}
return false;

  第一種比第二種更具有可讀性。

  #3. 在迴圈中刪除一個列表元素

  考慮下面的程式碼,迭代過程中刪除元素:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
	list.remove(i);
}
System.out.println(list);

  這段程式碼的輸出是:

[b, d]

  這個方法有一個嚴重的問題。當元素被移除,該列表的大小縮減,元素索引也隨之發生了變化。所以,如果你想通過使用索引來刪除一個迴圈內的多個元素,就會導致錯誤的結果。

  你可能猜到可以使用iterator來刪除迴圈中的元素。在Java中的foreach迴圈的工作原理就像一個iterator。 但是在這裡也會發生錯誤。請看下面的程式碼:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
 
for (String s : list) {
	if (s.equals("a"))
		list.remove(s);
}

  上面的foreach loop程式碼會丟擲一個異常ConcurrentModificationException. 但是下面這段程式碼不會。

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
	String s = iter.next();
 
	if (s.equals("a")) {
		iter.remove();
	}
}

  通過分析ArrayList.iterator()的原始碼,我們可以發現next()方法必須要在remove()方法前被呼叫。在foreach loop中,編譯器產生的程式碼會先呼叫next()方法,從而產生異常ConcurrentModificationException。請檢視ArrayList.iterator()的原始碼

  #4. Hashtable 與 HashMap

  按照演算法慣例,Hashtable是資料結構的名稱。但在Java中,資料結構的名稱是HashMap。Hashtable是同步的版本。所以很多時候你並不需要Hashtable,而是HashMap。 這兩篇文章詳細介紹了各種Map的區別和常見的問題: HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap, Map常見10大問題,

  #5.使用原始型別Collection

  在Java中,原始型別和無界萬用字元型別很容易混在一起。以Set為例,Set是原始型別,而Set<?>是無界萬用字元型別。

  考慮下面的程式碼,它使用原始型別的List作為引數:

public static void add(List list, Object o){
	list.add(o);
}
public static void main(String[] args){
	List<String> list = new ArrayList<String>();
	add(list, 10);
	String s = list.get(0);
}

  此程式碼將丟擲一個異常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at ...

  使用原始型別的Collection是危險的,因為原始型別的Collection跳過型別檢查。另外值得一提的是Set, Set<?>, Set<Object>之間存在著巨大的差異。 瞭解更多,請檢視原始型別 vs. 無界萬用字元型別型別擦除

  #6. 訪問級別

  很多時候,開發者使用public修飾欄位。這樣做的好處是很容易通過直接引用來獲取欄位的值,但是這是一個非常糟糕的設計。經驗法則是“給成員的訪問級別儘可能低”。可以檢視Java4種不同的訪問級別public, default, protected, and private

  #7. ArrayList 與 LinkedList

  當開發人員不知道ArrayList和LinkedList的區別的時候,他們經常使用的是ArrayList,可能因為它看起來面熟。但是ArrayList和LinkedList之間有巨大的效能差異。 簡單來說如果有大量的新增/刪除操作,而沒有很多隨機存取操作,LinkedList的應該是首選。可以檢視ArrayList與LinkedList瞭解它們之間更多的區別。

  #8.可變性與不變性

  不可變物件有很多優點,如簡單性,安全性等。但是它需要為每個不同的值創造一個單獨的物件,物件太多可能會導致垃圾回收的成本高。所以可變和不可變之間進行選擇時應該有一個平衡。

  一般情況下,使用可變物件,以避免產生過多的中間物件。一個經典的例子是串聯了大量的字串。如果使用的是不可變的字串String,會產生很多可以垃圾回收的物件。這樣既浪費時間也浪費CPU的運算能力,使用可變物件是正確的解決方案(如StringBuilder)。

String result="";
for(String s: arr){
	result = result + s;
}

  另外一些情況,可變物件剛更加合適可取。例如排序(Collections.sort())。如果Collection是不可變的,排序方法每次將會返回一個新的Collection,這樣會極其浪費資源。 可以看看為什麼在Java中String被設計成不可變?

  #9. 父類和子類的建構函式

  以上這段程式碼出現編譯錯誤,因為預設的父類建構函式未定義。在Java中,如果一個類沒有定義建構函式,編譯器會預設插入一個預設的無引數建構函式。如果程式設計師定義建構函式,編譯器將不插入預設的無引數建構函式。上面的程式碼由於自定義了有引數的建構函式,編譯器不再插入無引數的建構函式。子類的建構函式,無論是有引數或無引數,都將呼叫父類無參建構函式。當子類需要父類的無引數建構函式的時候,就發生了錯誤。

  解決這個問題,可以1)增加一個父類建構函式

public Super(){
    System.out.println("Super");
}

  ,或2)刪除自定義的父類建構函式,或3)新增super(value)到子類建構函式。更多請檢視父類和子類的建構函式

  #10. "" 與 Constructor?

  字串可以通過兩種方式建立:

//1. use double quotes
String x = "abc";
//2. use constructor
String y = new String("abc");

  這兩者有什麼區別呢? 下面的例子可以提供一個快速的答案:

String a = "abcd";
String b = "abcd";
System.out.println(a == b);  // True
System.out.println(a.equals(b)); // True
 
String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d);  // False
System.out.println(c.equals(d)); // True

  關於它們是如何分配記憶體的更多資訊,請檢視建立Java字串使用“”或建構函式

  小結

  以上是我根據GitHub上的開源專案,Stack Overflow上的問題,和谷歌熱門搜尋詞所做的總結。雖然它們不是準確的top 10,但很常見的。如果你有不同的觀點或者指出更常見的錯誤,請留言。我也會更新這個列表。非常感謝。

  英文原文: Top 10 Mistakes Java Developers Make

相關文章