知識點滴:Java程式設計師最常犯的Top10個錯誤問題簡述

俗話說的好:人非聖賢,孰能無過。都說Java語言是一門簡單的編程語言,基於C++演化而來,剔除了很多C++中的複雜特性,但這並不能保證Java程序員不會犯錯。那麼對於廣大的Java程序員來說,它們最常犯的10個錯誤是什麼呢?這裡通過總結出Java程序員最常犯的10大錯誤,可以有效地幫組Java後來者少走彎路,少加班,並寫出更健壯的應用程序。

知識點滴:Java程序員最常犯的Top10個錯誤問題簡述

1. 數組轉ArrayList

為了實現把一個數組轉換成一個ArrayList,很多Java程序員會使用如下的代碼:

知識點滴:Java程序員最常犯的Top10個錯誤問題簡述

Arrays.asList確實會返回一個ArrayList對象,但是該類是Arrays類 中一個私有靜態內部類,而不是常見的java.util.ArrayList類。這個java.util.Arrays.ArrayList類具有 set(),get(),contains()等方法,但是不具有任何添加或移除元素的任何方法。因為該類的大小(size)是固定的。為了創建出一個真正的ArrayList,代碼應該如下所示:

知識點滴:Java程序員最常犯的Top10個錯誤問題簡述

我們知道,ArrayList的構造方法可以接受一個Collection類型的對象,而我們的 java.util.Arrays.ArrayList正好也是它的一個子類。實際上,更加高效的代碼示例是:

知識點滴:Java程序員最常犯的Top10個錯誤問題簡述

2. 數組是否包含特定值

開發人員為了檢查數組中是否包含某個特定值,很多Java程序員會使用如下的代碼:

Set set = new HashSet(Arrays.asList(arr));

return set.contains(targetValue);

就功能而言,該代碼是正確無誤的,但在數組轉List,List再轉Set的過程中消耗了大量的性能。

它可以像下面這樣簡單:Arrays.asList(arr).contains(targetValue);

要麼:

for(String s: arr){

if(s.equals(targetValue))

return true;

}

return false;

3. 在迭代時移除List中的元素

下面的代碼在迭代過程中刪除元素:

ArrayList list = new ArrayList(Arrays.asList("a", "b", "c", "d"));

for (int i = 0; i < list.size(); i++) {

list.remove(i);

}

System.out.println(list);

這種方法存在嚴重的問題。當一個元素被刪除時,列表的大小會縮小,並且索引也會改變。所以,如果你想通過使用索引刪除循環內的多個元素,那將無法正常工作。

您可能知道使用迭代器是刪除循環內部元素的正確方法,並且您知道Java中的foreach循環像迭代器一樣工作,但事實上並非如此。考慮下面的代碼:

ArrayList list = new ArrayList(Arrays.asList("a", "b", "c", "d"));

for (String s : list) {

if (s.equals("a"))

list.remove(s);

}

它會拋出ConcurrentModificationException。

相反,以下是確定的:

ArrayList list = new ArrayList(Arrays.asList("a", "b", "c", "d"));

Iterator iter = list.iterator();

while (iter.hasNext()) {

String s = iter.next();

if (s.equals("a")) {

iter.remove();

}

}

.next()必須在之前被調用.remove()。在foreach循環中,編譯器會.next()在刪除元素的操作之後調用該函數,從而導致該操作ConcurrentModificationException。你可能想看看ArrayList.iterator()的源代碼。

4. Hashtable vs HashMap

學習過數據結構的讀者都知道一種非常重要的數據結構叫做哈希表。

在Java中,對應哈希表的的類是HashMap而不是Hashtable。

HashMap與Hashtable之間的最核心區別就是:

HashMap是非同步的,Hashtable是同步的。

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 list = new ArrayList();

add(list, 10);

String s = list.get(0);

}

這段代碼會拋出一個異常,使用原始類型集合是危險的,因為原始類型集合跳過了泛型類型檢查並且不安全。

6. 訪問權限

很多的Java初學者喜歡使用public來修飾類的成員。這樣可以很方便地直接訪問和存取該成員。但是,這是一種非常糟糕的編程風格,正確的設計風格應該是儘可能降低類成員的訪問權限。

7. ArrayList vs LinkedList

很多的Java初學者不明白ArrayList與LinkedList之間的區別,所以,他們完全只用相對簡單的ArrayList,甚至不知道JDK中還存在LinkedList。但是,在某些具體場景下,這兩種List的選擇會導致程序性能的巨大差異。

簡單而言:當應用場景中有很多的add/remove操作,只有少量的隨機訪問操作時,應該選擇LinkedList;在其他的場景下,考慮使用ArrayList。

8. 可變 vs 不可變

不可變的對象具有非常多的優勢,比如簡單,安全等。但是,對於每一個不同的值,都需要該類的一個對象。而且,生成很多對象帶來的問題就是可能導致頻繁的垃圾回收。所以,在選擇可變類還是不可變類時,應該綜合考慮後再做抉擇。

通常而言,可變對象可以避免創建大量的中間對象。一個非常經典的例子就是鏈接大量的短String對象為一個長的String對象。如果使用不可變String類,鏈接的過程將產生大量的,適合立即被垃圾回收的中間String對象,這將消耗大量的CPU性能和內存空間。此時,使用一個可變的StringBuilder或StringBuffer才是正確的。

除了上述情況,可變對象在其他場景下可能用於不可變對象。比如,傳遞一個可變的對象到方法內部,利用該對象可以收集多個結果,而不用在多個循環層次中跳進跳出。

9. 繼承中的構造函數

知識點滴:Java程序員最常犯的Top10個錯誤問題簡述

上圖中出現的兩個編譯時錯誤是因為:父類中沒有定義默認構造函數,而子類中又調用了父類的默認構造函數。在Java中,如果一個類不定義任何構造函數,編譯期將自動插入一個默認構造函數到給類中。一旦一個類定義了任何一個構造函數,編譯期就不會插入任何構造函數到類中。在上面的示例中,Super類定義了一個參數類型為String的構造函數,所以該類中只有一個構造函數,不會有默認構造函數了。

在我們的子類 Sub 中,我們定義了兩個構造函數:一個參數類型為String的構造函數,另一個為午餐的默認函數。由於它們都沒有在函數體的第一行指定調用父類的哪一個構造函數,所以它們都需要調用父類 Super 的默認構造函數。但是,父類 Super 的默認構造函數是不存在的,所以編譯器報告了這兩個錯誤信息。

10. 字符串對象的兩個構建方式

Java中的字符串對象具有兩個常見的創建方式:

//1、使用雙引號

String str = "xyz" ;

//2、使用構造器

String str = new String("xyz");

它們之間的區別是什麼呢?我們再看一下如下的代碼:

知識點滴:Java程序員最常犯的Top10個錯誤問題簡述

說明:引號創建的字符串會先在java內存的常量池中查找是否有對應的字符串,如果有則直接引用原來的字符串常亮,如果沒有就把新的字符串常亮放入常量池;如果new創建的字符串,則不是存放在常量池的,而是在內存中創建新的對象,每次new的對象其變量引用的地址都不相同,所以你直接“==”判斷的值是不一樣的(判斷對象地址)返回都是false,但如果是用equal判斷,就有可能是true相等的,因為它判的是對象內容。

備註:相關問題來源於網絡,茲以彙總,為初學者提供幫助^_^


分享到:


相關文章: