我最近在學習 Java,以爲這篇舊文不錯,就翻譯了一下,感受對新手有些幫助。java
原文:www.programcreek.com/2014/05/top…算法
顧問:張博(Gradle 開發者之一)安全
譯文開始bash
這 10 個錯誤是我綜合 GitHub 上的項目、StackOverflow 上的問答和 Google 搜索關鍵詞的趨勢而分析得出的。數據結構
一些開發者常常用這樣的代碼將 Array 轉換成 ArrayList函數
List<String> list = Arrays.asList(arr);
複製代碼
Arrays.asList() 的返回值是一個 ArrayList 類的對象,這個 ArrayList 類是 Arrays 類裏的一個私有靜態類(java.util.Arrays.ArrayList),並非 java.util.ArrayList 類。性能
java.util.Arrays.ArrayList 有 set() / get() / contains() 方法,可是並不提供任何添加元素的方法,所以它的長度是固定的。若是你但願獲得一個 java.util.ArrayList 類的實例,你應該這麼作:學習
ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
複製代碼
ArrayList 的構造函數能夠接受一個 Collection 實例,而 Collection 是 java.util.Arrays.ArrayList 的超類。ui
一些開發者會這麼寫:this
Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);
複製代碼
上面的代碼能夠工做,可是其實不必把 list 轉爲 set,這有些浪費時間,簡單的寫法是這樣的:
Arrays.asList(arr).contains(targetValue);
複製代碼
或者這樣的
for(String s: arr){
if(s.equals(targetValue))
return true;
}
return false;
複製代碼
這兩種寫法中,前者可讀性更好。
下面的代碼在迭代時移除了元素:
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]
複製代碼
這種代碼的問題在於,當元素被移除時,list 的長度也隨之變小了,index 也同時發生了變化。因此,若是你想要在循環中使用 index 移除多個元素,它可能不能正常工做。
你可能認爲在循環中刪除元素的正確方法是迭代器,好比 foreach 循環看起來就是一個迭代器,其實這樣仍是有問題。
考慮如下代碼(代碼 1):
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (String s : list) {
if (s.equals("a"))
list.remove(s);
}
複製代碼
會拋出 ConcurrentModificationException 異常。
要正確地在遍歷時刪除元素,應該這麼寫(代碼 2):
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();
}
}
複製代碼
你必須在每次循環裏先調用 .next() 再調用 .remove()。
在代碼 1 中的 foreach 循環中,編譯器會在元素的刪除操做以後調用 .next(),致使 ConcurrentModificationException 異常,若是你想深刻了解,能夠看看 ArrayList.iterator() 的源碼。
通常來講,算法中的 Hashtable 是一種常見的數據結構的名字。可是在 Java 中,這種數據結構的名字倒是 HashMap,不是 Hashtable。Java 中 Hashtable 和 HashMap 的最重要的區別之一是 Hashtable 是同步的(synchronized)。所以大部分時候你不須要用 Hashtable,應該用 HashMap。
在 Java 中,「原始類型」和「無限制通配符類型」很容易被搞混。舉例來講,Set 是一個原始類型,而 Set<?> 是一個無限制通配符類型。
下面的代碼中的 add 接受原始類型 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 是很危險的,由於原始類型沒有泛型檢查。Set / Set<?> / Set<Object>
之間有很是大的差別,詳情能夠看看《Set vs. Set<?>》和《Java Type Erasure Mechanism》。
不少開發者爲了省事,把類字段標記爲 public,這不是個好習慣。好習慣應該是將訪問級別設置得越低越好。
詳見《public, default, protected, and private》。
若是不瞭解 ArrayList 和 LinkedList 的區別,你很容易會傾向於使用 ArrayList,由於它看起來更常見。
可是,ArrayList 和 LinkedList 有巨大的性能差別。簡單來講,若是 add/remove 操做較多,則應該使用 LinkedList;若是隨機訪問操做較多,則應該使用 ArrayList。
若是你想深刻了解這些性能差別,能夠看看《ArrayList vs. LinkedList vs. Vector》。
不可變對象有不少好處,好比簡單、安全等。可是不可變對了要求每次改動都生成新的對象,對象一多就容易對垃圾回收形成壓力。咱們應該在可變對象和不可變對象上找到一個平衡點。
通常來講,可變對象能夠避免產生太多中間對象。一個經典的例子就是鏈接大量字符串。若是你使用不可變字符串,你就會造出許多用完即棄的中間對象。這既浪費時間又消耗 CPU,因此這種狀況下你應該使用可變對象,如 StringBuilder:
String result="";
for(String s: arr){
result = result + s;
}
複製代碼
還有一些狀況值得使用可變對象。好比你能夠經過將可變對象傳入方法來收集多個結果,從而繞開語法的限制。再好比排序和過濾操做,雖然你能夠返回新的被排序以後的對象,可是若是元素數量衆多,這就會浪費很多內存。
擴展閱讀《爲何字符串是不可變的》。
class Super {
String s;
public Super(String s){
this.s = s;
}
}
public class Sub extend Super{
int x = 200;
public Sub(String s){ // 編譯錯誤
}
public Sub(){ // 編譯錯誤
System.out.println("Sub");
}
public static void main(String[] args){
Sub s = new Sub();
}
}
複製代碼
上述代碼會有編譯錯誤,由於沒有實現 Super() 構造函數。Java 中,若是一個類沒有定義構造函數,編譯器將會插入一個默認的沒有參數的構造函數。可是若是 Super 類已經有了一個構造函數 Super(String s),那麼編譯器就不會插入這個默認的無參數的構造函數。這就是上述代碼的遇到的狀況。
Sub 類的兩個構造函數,一個有參數一個沒有參數,都會調用 Super 類的無參數構造函數。由於編譯器會嘗試在 Sub 類的兩個構造函數裏插入 super()
,因爲 Super 類沒有無參數構造函數,因此編譯器就報錯了。
解決這個問題,有三種方法:
想了解更多詳情,能夠看《Constructors of Sub and Super Classes in Java?》。
字符串能夠經過兩種途徑來構造:
// 1. 使用雙引號
String x = "abd";
// 2. 使用構造函數
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
複製代碼
想了解這兩種方式生成的字符串在內存中是如何存在的,能夠看看《Create Java String Using 」 」 or Constructor?》
這 10 個錯誤是我綜合 GitHub 上的項目、StackOverflow 上的問答和 Google 搜索關鍵詞的趨勢而分析得出的。它們可能並非真正的 10 個最多的錯誤,但仍是挺廣泛的。若是你有異議,能夠給我留言。若是你能告訴我其餘常見的錯誤,我會很是感謝你。