1. 程式人生 > >205K+程式設計師關注過的問題:為什麼不應該使用Java的原始型別?

205K+程式設計師關注過的問題:為什麼不應該使用Java的原始型別?

在逛 Stack Overflow 的時候,發現了一些訪問量像熊耳山一樣高的問題,比如說這個:為什麼不應該使用Java的原始型別?訪問量足足有 205K+,這不得了啊!說明有很多很多的程式設計師被這個問題困擾過。實話實說吧,本文之前的我就是其中之一。

來回顧一下提問者的問題吧:

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、鳴謝

好了各位讀者朋友們,以上就是本文的全部內容了。能看到這裡的都是最優秀的程式設計師,二哥必須要動動手為你點個贊