205K+程序員關注過的問題:為什麼不應該使用Java的原始類型?
- 2019 年 12 月 24 日
- 筆記
來回顧一下提問者的問題吧:
Java 的原始類型是什麼?為什麼不要使用原始類型?如果不能使用原始類型,有什麼更好的選擇呢?
如果大家也被這個問題困擾過,或者正在被困擾,就請隨我來,咱們肩並肩手拉手一起梳理一下這個問題,並找出最佳答案。Duang、Duang、Duang,打怪進階嘍!
01、Java 的原始類型是什麼?
要理解 Java 的原始類型是什麼,可以先看一下什麼是泛型。
List<String> list = null;
其中 list 就是一個泛型,我們通常稱之為字符串(String)列表(List),也就是說 list 中只能放字符串類型的元素。
如果我們按照下面這種方式聲明 list 的話,它就是一個原始類型。
List list = null;
從 list 的聲明當中我們可以對比發現,原始類型沒有為容器指定明確的元素類型,所以我們可以在容器中放入一個 String,也可以放入一個 Integer,甚至任意的類型,就像下面這樣。
public class RawType { public static void main(String[] args) { List list = new ArrayList(); list.add("沉默王二"); list.add(18); list.add(new RawType()); } }
注意哦,編譯器沒有任何提醒!這預示着 Java 這門強類型的語言竟然有點弱類型的影子了。
PS:關於 Java 中的類型術語,大家可以參照下表。
術語 |
含義 |
舉例 |
---|---|---|
Parameterized type |
參數化類型 |
List<String> |
Actual type parameter |
實際類型參數 |
String |
Generic type |
泛型類型 |
List<E> |
Formal type parameter |
形式類型參數 |
E |
Unbounded wildcard type |
無限制通配符類型 |
List<?> |
Raw type |
原始類型 |
List |
Bounded type parameter |
限制類型參數 |
<E extends Number> |
Bounded wildcard type |
限制通配符類型 |
List<? extends Number> |
02、為什麼不要使用原始類型?
大家可能會有一個疑惑,原始類型用起來很爽啊!因為不用關心放入 List 的元素到底是什麼類型,想放什麼就可以放什麼,不要太爽啊!
可當我們想要從 List 中把元素取出來使用的時候,可就遇到大麻煩了。
List list = new ArrayList(); list.add("沉默王二"); list.add(18); list.add(new RawType()); for (Object o : list ) { String s = (String) o; System.out.println(s); }
上面這段代碼編譯的時候沒有任何問題,但輸出的時候就會拋出 ClassCastException
。
沉默王二 Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at com.cmower.java_demo.programcreek.RawType.main(RawType.java:14)
除非我們使用 instanceof
關鍵字進行類型判斷,就像下面這樣。
List list = new ArrayList(); list.add("沉默王二"); list.add(18); list.add(new RawType()); for (Object o : list ) { if (o instanceof String) { String s = (String) o; System.out.println(s); } else if (o instanceof Integer) { Integer i = (Integer) o; System.out.println(i); } else if (o instanceof RawType) { RawType raw = (RawType) o; System.out.println(raw); } }
可假如代碼寫成這樣,可真真算得上是糟糕的代碼了。
通常來說,為了代碼的安全性起見,我們希望代碼的錯誤發生得越早越好,能在編譯時就不要在運行時。可使用原始類型的時候,我們發現錯誤一直到運行時才可能會被檢出。
還記得《扁鵲見蔡桓公》的故事嗎?
扁鵲見蔡桓公,立有間。扁鵲曰:「君有疾在腠理,不治將恐深。」桓侯曰:「寡人無疾。」扁鵲出,桓侯曰:「醫之好治不病以為功。」……居十日,扁鵲望桓侯而還走。桓侯故使人問之,扁鵲曰:「疾在腠理,湯熨之所及也;在肌膚,針石之所及也;在腸胃,火齊之所及也;在骨髓,司命之所屬,無奈何也。今在骨髓,臣是以無請也。」居五日,桓侯體痛,使人索扁鵲,已逃秦矣。桓侯遂死。
病情發現得越早,治療的可能性就越大。同理,代碼隱藏的問題發現的越晚,找出根源花費的精力就越大、時間就越多。
03、有什麼更好的選擇呢?
如果不能使用原始類型,有什麼更好的選擇呢?
為了讓 List 能夠容納任意類型的元素,我們可以使用 List<Object>
,儘管這並不是一個最優的選擇。
List<Object> list = new ArrayList<>(); list.add("沉默王二"); list.add(18); list.add(new RawType());
鵝鵝鵝,這樣的參數化類型 List<Object>
和原始類型 List 之間有區別嗎?
當然有了!
List<Object>
至少明確地告訴編譯器,該容器可以存放任意類型的對象,沒有丟失類型的安全性。
可能我這樣的解釋會遭到某些抨擊:「這不五十步笑百步嗎?呵呵。」但我要想表達的是登月男神阿姆斯特朗的那句話:「這是我個人的一小步,卻是人類的一大步。」能向前邁一步是一步啊。
那最優的選擇是什麼呢?
從一開始就為 List 聲明具體的類型,比如說 List<String> list
,當我們嘗試放入一個 int 值的時候就會編譯出錯。

從另一種層面上來說,這樣做削弱了程序的靈活性,但保證了程序的絕對安全性,以及在表達上的明確性。
04、為什麼 Java 允許使用原始類型?
既然原始類型是不安全的,那為什麼 Java 一直允許使用原始類型呢?並且泛型擦除後仍然是個原始類型呢?
答案很簡單、很無厘頭、很蒼白——為了版本兼容!
引入泛型的時候,Java 已經進入到第二個十年(年紀大了),市面上存在大量沒有使用 Java 泛型的代碼。如果因為版本升級導致它們不能使用,恐怕 Java 也活不到現在,畢竟對用戶友好才是一個軟件存在的硬道理。
當然了,Java 已經對開發者做出了警示:強烈建議不要在 Java 代碼中使用原始類型,未來的版本中可以會禁止使用原始類型,請小心點。
05、鳴謝
好了各位讀者朋友們,以上就是本文的全部內容了。能看到這裡的都是最優秀的程序員,二哥必須要動動手為你點個贊?。如果覺得不過癮,還想看到更多,我再推薦幾篇給大家。