這 10 個錯誤是我綜合 GitHub 上的項目、StackOverflow 上的問答和 Google 搜索關鍵詞的趨勢而分析得出的。
1 將 Array 轉換成 ArrayList 時出錯
一些開發者經常用這樣的代碼將 Array 轉換成 ArrayList
List 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 arrayList = new ArrayList(Arrays.asList(arr));復制代碼
ArrayList 的構造函數可以接受一個 Collection 實例,而 Collection 是 java.util.Arrays.ArrayList 的超類。
2 檢查 array 里是否含有某個值時出錯
一些開發者會這么寫:
Setset= new HashSet(Arrays.asList(arr));returnset.contains(targetValue);復制代碼
上面的代碼可以工作,但是其實沒必要把 list 轉為 set,這有些浪費時間,簡單的寫法是這樣的:
Arrays.asList(arr).contains(targetValue);復制代碼
或者這樣的
for(String s: arr){if(s.equals(targetValue))returntrue;}returnfalse;復制代碼
這兩種寫法中,前者可讀性更好。
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);復制代碼
得到的結果是
[b, d]復制代碼
這種代碼的問題在于,當元素被移除時,list 的長度也隨之變小了,index 也同時發生了變化。所以,如果你想要在循環中使用 index 移除多個元素,它可能不能正常工作。
你可能認為在循環中刪除元素的正確方法是迭代器,比如 foreach 循環看起來就是一個迭代器,其實這樣還是有問題。
考慮以下代碼(代碼 1):
ArrayList list = new ArrayList(Arrays.asList("a","b","c","d"));for(String s : list) {if(s.equals("a"))list.remove(s);}復制代碼
會拋出 ConcurrentModificationException 異常。
要正確地在遍歷時刪除元素,應該這么寫(代碼 2):
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()。
在代碼 1 中的 foreach 循環中,編譯器會在元素的刪除操作之后調用 .next(),導致 ConcurrentModificationException 異常,如果你想深入了解,可以看看 ArrayList.iterator() 的源碼。
4 用 Hashtable 還是用 HashMap
一般來說,算法中的 Hashtable 是一種常見的數據結構的名字。但是在 Java 中,這種數據結構的名字卻是 HashMap,不是 Hashtable。Java 中 Hashtable 和 HashMap 的最重要的區別之一是 Hashtable 是同步的(synchronized)。因此大部分時候你不需要用 Hashtable,應該用 HashMap。
5 直接使用 Collection 的原始類型時出錯
在 Java 中,「原始類型」和「無限制通配符類型」很容易被搞混。舉例來說,Set 是一個原始類型,而 Set<?> 是一個無限制通配符類型。
下面的代碼中的 add 接受原始類型 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);}復制代碼
這個代碼會在運行時才拋出異常:
Exceptioninthread"main"java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Stringat ...復制代碼
使用原始類型的 collection 是很危險的,因為原始類型沒有泛型檢查。Set / Set<?> / Set<Object>?之間有非常大的差異,詳情可以看看《Set vs. Set<?>》和《Java Type Erasure Mechanism》。
6 訪問級別設置過高
很多開發者為了省事,把類字段標記為 public,這不是個好習慣。好習慣應該是將訪問級別設置得越低越好。
詳見《public, default, protected, and private》。
7 ArrayList 和 LinkedList 選用錯誤
如果不了解 ArrayList 和 LinkedList 的區別,你很容易會傾向于使用 ArrayList,因為它看起來更常見。
但是,ArrayList 和 LinkedList 有巨大的性能差異。簡單來說,如果 add/remove 操作較多,則應該使用 LinkedList;如果隨機訪問操作較多,則應該使用 ArrayList。
如果你想深入了解這些性能差異,可以看看《ArrayList vs. LinkedList vs. Vector》。
8 可變還是不可變?
不可變對象有很多好處,比如簡單、安全等。但是不可變對了要求每次改動都生成新的對象,對象一多就容易對垃圾回收造成壓力。我們應該在可變對象和不可變對象上找到一個平衡點。
一般來說,可變對象可以避免產生太多中間對象。一個經典的例子就是連接大量字符串。如果你使用不可變字符串,你就會造出許多用完即棄的中間對象。這既浪費時間又消耗 CPU,所以這種情況下你應該使用可變對象,如 StringBuilder:
String result="";for(String s: arr){result = result + s;}復制代碼
還有一些情況值得使用可變對象。比如你可以通過將可變對象傳入方法來收集多個結果,從而繞開語法的限制。再比如排序和過濾操作,雖然你可以返回新的被排序之后的對象,但是如果元素數量眾多,這就會浪費不少內存。
擴展閱讀《為什么字符串是不可變的》。
9 超類和子類的構造函數
class Super {? String s;? public Super(String s){? ? this.s = s;? }}public class Sub extend Super{? int x = 200;? public Sub(String s){ // 編譯錯誤? }? publicSub(){? // 編譯錯誤? ? 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 類沒有無參數構造函數,所以編譯器就報錯了。
解決這個問題,有三種方法:
給 Super 類添加一個 Super() 無參數構造函數
刪掉 Super 類里的有參數的構造函數
在 Sub 類的構造函數里添加 super(value)
想了解更多詳情,可以看《Constructors of Sub and Super Classes in Java?》。
10 用 "" 還是用構造函數
字符串可以通過兩種途徑來構造:
// 1. 使用雙引號String x ="abd";// 2. 使用構造函數String y = new String("abc");復制代碼
有什么區別呢?
下面的代碼可以很快地告訴你區別:
String a ="abcd";String b ="abcd";System.out.println(a == b);? // TrueSystem.out.println(a.equals(b)); // True String c = new String("abcd");String d = new String("abcd");System.out.println(c == d);? // FalseSystem.out.println(c.equals(d)); // True復制代碼
想了解這兩種方式生成的字符串在內存中是如何存在的,可以看看《Create Java String Using ” ” or Constructor?》
總結
這 10 個錯誤是我綜合 GitHub 上的項目、StackOverflow 上的問答和 Google 搜索關鍵詞的趨勢而分析得出的。它們可能并不是真正的 10 個最多的錯誤,但還是挺普遍的。如果你有異議,可以給我留言。如果你能告訴我其他常見的錯誤,我會非常感謝你。當然除了注意這些容易放的錯誤,還是要不斷的一直提升自己。在這里小編分享一份自己工作中提升自己的學習經驗,希望能幫助到大家。
從事java十余年,現在把架構師必須具備的一些技術總結出來一套思維導圖和錄制了一些相關視頻,分享給大家,供大家參考。
需要相關資料可以加群:810589193,點擊鏈接加入群聊【Java架構學習交流群】:https://jq.qq.com/?_wv=1027&k=5deQUBl
我把它分為六個點
1. 高性能架構
1.1. 分布式架構思維
1.2. Zookeeper分布式環境指揮官
1.3. Nginx高并發分流進階實戰
1.4. ActiveMq消息中間件
1.5. RabbitMq消息中間件
1.6. Kafka百萬級吞實戰
1.7. Memcached進階實戰
1.8. Redis高性能緩存數據庫
1.9. MongoDB進階實戰
1.10. 高性能緩存開發實戰
1.11. Mysql高性能存儲實戰
1.12. FastDFS分布式文件存儲實戰
1.13. 高并發場景分布式解決方案實戰
2. 微服務架構
2.1. 服務的前世今生
2.2. 基于分布式思想下的RPC解決方案
2.3. Dubbo應用及源碼解讀
2.4. SpringBoot
2.5. SpringCloud應用及源碼解讀
2.6. Docker虛擬化技術
3. 開源框架
3.1. spring5概述
3.2. Spring5 Framework體系結構
3.3. Spring5環境搭建
3.4. IOC源碼解析
3.5. AOP源碼解析
3.6. Spring MVC
3.7. Mybatis
4. 架構師基礎
4.1. JVM性能調優
4.2. Java程序性能優化
4.3. Tomcat
4.4. 并發編程進階
4.5. Mysql
4.6. 高性能Netty框架
4.7. Linux基礎與進階
5. 團隊協作開發
5.1. Git
5.2. Maven
5.3. Jenkins
5.4. Sonar
6. B2C商城項目
6.1. 系統設計
6.2. 用戶管理子系統
6.3. 商品管理子系統
6.4. 搜索子系統
6.5. 訂單子系統
6.6. 支付系統
6.7. 分布式調度系統
6.8. 后臺系統
不是每個人都能成為高手,但是不努力,就算有再高的天分,也白癡一個!如果你想學習 Java 工程化、高性能及分布式、高性能、深入淺出。性能調優、Spring,MyBatis,Netty 源碼分析和大數據等知識點可以來找我。
工作一到五年的程序員朋友面對目前的技術無從下手,感到很迷茫可以加群:810589193,點擊鏈接加入群聊【Java架構學習交流群】:https://jq.qq.com/?_wv=1027&k=5deQUBl里面有阿里Java高級大牛直播講解知識點,分享知識,課程內容都是各位老師多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!