關於 Swift & Java 中 Optional 的設計

更新於 發佈於 閱讀時間約 8 分鐘
此為過去的舊文,2014 年 7 月 6 日初次發表於 logdown。

近幾年電腦的運算效能已經好到一個程式語言的抽象程度遠比執行效率重要的層級,所以最近超多種程式語言冒出來(有種好累的感覺),特別是 Domain Specific Language,不過今天討論的還是 Genernal Purpose Language,只是在語法的抽象程度都過去的語言要好。

WWDC 2014 後就開始看《The Swift Programming Language》,看到 optionsals時,總覺得這好像看過耶,原來是在當初研究 Java 的 Stream API (參閱《Java 8 初探 - Stream》) 時,就看過 Java 8 內建相同的概念,但概念相同,實作卻不全然一樣,Java 的 Optional API level 的支援,Swift 則是從 language level 做支援。但如果真要說誰抄誰就很難說了 (ScalaOption 或是 Groovy 的 safe navigation operator 都是相似的的設計),畢竟最近的幾個新程式語言都從 functional programming language 借了很多特色,幾乎都支援 Lambda 就是一個明顯的例子。

不管是 Scala、Java 或是 Swift,概念上,Optioanl 是一個容器,裡面可能有值也可能沒有值,但是這個容器本身絕對不是 null (Java) 或 nil (Swift),因此在操作這個容器時,是絕對不會拋出NullPointerException (說是這麼說,但 Java 確實可以回傳空的 Optional,只能靠開發者堅守慣例才能讓這件事成真),但如果無視裡面是否有值就硬要取值還是會拋出 NoSuchElementException (Java) 或 Runtime error (Swift)。但是多了一層 Optional 是否真的能提高抽象程度呢?畢竟『是否有值』這件事還是得判斷,難道用了 Optional 就可以省去什麼麻煩嗎?首先看 Java 使用 Optional 後,使用上的差異吧!

以 Java 來說,後者搭配新的 Lambda expression 確實看起來賞心悅目許多。接著看 Swift 的例子,Swift的 if 和 Java一樣,只接受能產生 boolean 為結果的述句 (expression),所以 if possibleName 這個判斷式在解讀上和 C/C++ 不同 (C/C++ 的 if 是判斷述句的結果是否為非 0),但 Swift 是對 possibleName 這個 optional 物件先進行 evaluaton,若結果為 true,表示該物件是有值的,才執行大括弧裡的程式片段。除此之外,Swift 還有一種 optional binding 機制,即 if let name = possibleName,這一行的解讀為:先對 possibleName 物件進行evaluation,若結果為 true,則將 possibleName 所代表的物件指派給 name,因此在大括弧中 name物件保證是非 nil 可以安全存取的。


就上述的例子,是否有覺得抽象程度提高呢?或許再看二個例子吧,假設在使用某個沒有 API 文件的函式庫時,有 optioanl 和沒 optional 哪個版本能比較清楚知道解碼這個函式可能回傳一個不存在的物件呢?我想這應該很明顯,有 optional 的版本應該清楚很多,所以對我來說 optional 的引入,第一個好處是在做 API 設計時,可以提供一個很明確的回傳值定義,而不是透過文件的方式解釋回傳值可能不存在的情況。


剛才提到 Swift 對 optional 是 language level 的支援,除了 if 會自動對 optional 物件進行 evaluation和提供 optional binding 外,和 Groovy 一樣提供 optional chaining。假設 Person 物件有個可能不存在的 residence 屬性,代表其居住地,型別為 ResidenceResidenceAddress 紀錄地址,同樣可能不存在,Address 有個 street 屬性紀錄街名,同樣可能不存在,所以想透過 person 物件存取居住地的地址街名時,除了用 optional binding 層層解開外,Swift 提供 optional chaining:person.residence?.address?.street,只要在這串存取中任何一個屬性是不存在的,if 就會得到 false,也就不會執行指定的區塊,程式看起來較清爽簡潔許多。


那 Java 如何呢?Java 對 Optional 的支援大多是以 API 的形式存在,以剛剛的例子,若不想檢查 null,如下所示,需要搭配 Stream API 來使用,map(Function) 透過傳入的 Function 物件將 Optional<Person> 依序換轉 (想像成取得property) 成 Optional<Residence>Optional<Address>Optional<String>,最後用ifPresent(Consumer) 印出結果,就簡潔度來說 optional binding 確實簡潔多了。就抽象程度來說,用 Stream API 還真的需要一點想像力才能寫出這樣的程式碼,所以對我來說 optional chaining 的抽象度還是比較高一點。


整體來說,不論是 Swift 或是 Java,使用 Optional 來設計 API (注意,如果最後的例子中 getter 的回傳值是 Optional,那要用 flatMap(Function) 取代 map(Function),不然會拿到類似 Optional<Optional<Residence>> 的結果)應該都能提供更清楚的語義:該值可能不存在。

只是 Swift 以 language level 支援 Optional 確實比用 API level 支援的 Java 要簡潔和更具可讀性。不過,Java 是一個歷史悠久 (1995至今) 的語言,很難對 language 本身做出太大幅度的改變,反之,Swift 是一個全新的語言,從一開始的設計就將許多好的語言特性加入,確實讓人驚豔。


延伸閱讀


2024 補充

10 年過去,Java 已經敲敲升級到 Java 21,但在處理 null 上,仍然沒太多改善,但不難理解,對一個已經有廣大生態系的語言,以語言層級的方式支援 Optional,確實有點動搖國本的感覺,例如,真的類似 Swift 等語言,用 ? 代表一個 Optional 物件,假設既有的函數庫中,一個函式回傳一個物件,因為已經編譯成 JAR 檔了,原作者沒空處理,在新版本裡使用這個函式,編譯器應該視回傳值為 Optional 還是非 Optional?要保證安全,應該要視為 Optional,因為舊版的 Java 確實允許回傳空值,這時編譯器會報錯 (或是警告),光處理這些錯誤或警告,就是一件辛苦的差事。若視為非 Optional,等到執行時真的是 null,恐怕也不適合。但如果真要選,我會選前者,並將這類問題視為警告,讓開發者慢慢朝向新特性邁進,晚到遲到總比不到好。

avatar-img
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
這幾年新出的語言都強打在少寫 code 和提高可讀性,更重要的是能更容易發展出 domain specific language,就這一點 Java 確實有點顯得疲態了。其實文中列的特性大多是一些語法糖衣,但對程式的可讀性和抽象度都能提昇不少,我覺得挺實用也很划算的。
很明顯可以看到 parallelSort(T[], Comparator<T> 大概可以帶來 2.5 倍到接近 3 倍的效能增益 (和數量無關)。所以,結論是當需要處理大量資料的排序時,真的可以考慮使用 parallelSort(T[], Comparator<T>。
Java 8 有了 Base64 編解碼器,方便不少,不過 Apache Commons Codec 提供更多常用的編解碼器,其實是更方便的,但如果你的應用程式中只需要 Base64 編解碼器,在有 Java 8 的環境中確實不需要將 Apache Commons Codec 和專案一起打包。
default methods 似乎也引起不小的討論,因為 default methods 加上可以實作多個介面,已經有點像 C++ 的多重繼承了,只差在沒辦法繼承成員變數而已,是好是壞就看怎麼使用了。我個人覺得還蠻方便的
Lazy evaluation 的效益必須是在 pipe 的組合上有最佳化過的,若組合的不好反而更糟糕,且在 I/O 上幫助似乎也不大。parallel stream 要能發揮效果必須看資料的來源類型,不過要注意的是 parallel stream 也會使記憶體的使用量增加,使用上也要小心。
老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
這幾年新出的語言都強打在少寫 code 和提高可讀性,更重要的是能更容易發展出 domain specific language,就這一點 Java 確實有點顯得疲態了。其實文中列的特性大多是一些語法糖衣,但對程式的可讀性和抽象度都能提昇不少,我覺得挺實用也很划算的。
很明顯可以看到 parallelSort(T[], Comparator<T> 大概可以帶來 2.5 倍到接近 3 倍的效能增益 (和數量無關)。所以,結論是當需要處理大量資料的排序時,真的可以考慮使用 parallelSort(T[], Comparator<T>。
Java 8 有了 Base64 編解碼器,方便不少,不過 Apache Commons Codec 提供更多常用的編解碼器,其實是更方便的,但如果你的應用程式中只需要 Base64 編解碼器,在有 Java 8 的環境中確實不需要將 Apache Commons Codec 和專案一起打包。
default methods 似乎也引起不小的討論,因為 default methods 加上可以實作多個介面,已經有點像 C++ 的多重繼承了,只差在沒辦法繼承成員變數而已,是好是壞就看怎麼使用了。我個人覺得還蠻方便的
Lazy evaluation 的效益必須是在 pipe 的組合上有最佳化過的,若組合的不好反而更糟糕,且在 I/O 上幫助似乎也不大。parallel stream 要能發揮效果必須看資料的來源類型,不過要注意的是 parallel stream 也會使記憶體的使用量增加,使用上也要小心。
老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
我自己是希望可以製作iOS app來更好存放我的文章, 更進階一點,可以變成直接錄音後, 照我設定的方式轉換成文檔,讓iPhone變成我更強的助手。 感覺有很多可以探索,用時間慢慢累積經驗。
Thumbnail
這篇文章的目的是對Java程式設計語言進行介紹,包括它的特性、應用範疇、主要使用者,以及相關的學習資源和常見的庫與框架。此外,它也提供了一些學習Java的渠道,以及與Java相關的其他知識。
Thumbnail
本文檔介紹了在Swift中使用套件的詳細方法,包括如何引用第三方套件和自定義模組,如何創建自定義套件,以及一些常見的Swift套件。這些套件可以幫助開發者快速添加功能到項目中,提高開發效率和程式碼品質。
Thumbnail
此章節旨在解釋Swift語言中函數的基本結構和操作方式,包括函數的聲明、呼叫、參數和返回值。閱讀這個章節可以幫助你理解並掌握如何在Swift編程中有效地使用和管理函數。
Thumbnail
本篇介紹了Swift程式語言中的各種流程控制元素,包括條件語句(如if, else if, else),三元運算子,多條件分支判斷的switch語句,以及各種迴圈(如for迴圈,while迴圈,以及repeat-while迴圈)。同時也詳細解釋了如何進行迴圈嵌套,以及如何使用控制迴圈語句。
Thumbnail
本章節介紹了程式語言中的各種運算符,包括算數運算子、比較運算子、賦值運算子、位元運算子,以及運算子的優先等級。每種運算子都有對應的範例程式碼和輸出結果,以幫助讀者更好地理解其用法和效果。此外,章節也強調了運算子的優先等級在程式設計中的重要性,並通過範例展示了不同優先等級的運算順序對運算結果的影響。
Thumbnail
此章節旨在介紹Swift程式語言中的基本資料型別,包括整數、浮點數、布林型別、字串、字符、選擇型、型別轉換、自訂型別(包括類、結構和枚舉)、元組型別、集合型別、陣列型別和字典型別。每種型別的說明都包含了程式碼範例,以便讀者更好地理解其應用和使用方式。
Thumbnail
本章節介紹了如何建立並設置Swift項目以及如何選擇和設置Swift代碼編輯器。這包括在Xcode和命令行中建立Swift項目,選擇Xcode、Visual Studio Code或AppCode作為編輯器,以及如何使用SPM安裝插件。
Thumbnail
本章節旨在為讀者提供Swift程式語言的基礎知識,包括其基本語法、註解方法和變數使用方式,並通過具體的程式碼示例來說明這些概念。這將幫助讀者理解Swift的基本結構,並學會如何在Swift中定義變數並使用註解。
Thumbnail
這份文件的目的是介紹Swift語言,包括它的特性、應用範疇,以及誰在使用它。它也提供了一些學習Swift的資源和工具,以及一些常見的Swift庫和框架。
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
我自己是希望可以製作iOS app來更好存放我的文章, 更進階一點,可以變成直接錄音後, 照我設定的方式轉換成文檔,讓iPhone變成我更強的助手。 感覺有很多可以探索,用時間慢慢累積經驗。
Thumbnail
這篇文章的目的是對Java程式設計語言進行介紹,包括它的特性、應用範疇、主要使用者,以及相關的學習資源和常見的庫與框架。此外,它也提供了一些學習Java的渠道,以及與Java相關的其他知識。
Thumbnail
本文檔介紹了在Swift中使用套件的詳細方法,包括如何引用第三方套件和自定義模組,如何創建自定義套件,以及一些常見的Swift套件。這些套件可以幫助開發者快速添加功能到項目中,提高開發效率和程式碼品質。
Thumbnail
此章節旨在解釋Swift語言中函數的基本結構和操作方式,包括函數的聲明、呼叫、參數和返回值。閱讀這個章節可以幫助你理解並掌握如何在Swift編程中有效地使用和管理函數。
Thumbnail
本篇介紹了Swift程式語言中的各種流程控制元素,包括條件語句(如if, else if, else),三元運算子,多條件分支判斷的switch語句,以及各種迴圈(如for迴圈,while迴圈,以及repeat-while迴圈)。同時也詳細解釋了如何進行迴圈嵌套,以及如何使用控制迴圈語句。
Thumbnail
本章節介紹了程式語言中的各種運算符,包括算數運算子、比較運算子、賦值運算子、位元運算子,以及運算子的優先等級。每種運算子都有對應的範例程式碼和輸出結果,以幫助讀者更好地理解其用法和效果。此外,章節也強調了運算子的優先等級在程式設計中的重要性,並通過範例展示了不同優先等級的運算順序對運算結果的影響。
Thumbnail
此章節旨在介紹Swift程式語言中的基本資料型別,包括整數、浮點數、布林型別、字串、字符、選擇型、型別轉換、自訂型別(包括類、結構和枚舉)、元組型別、集合型別、陣列型別和字典型別。每種型別的說明都包含了程式碼範例,以便讀者更好地理解其應用和使用方式。
Thumbnail
本章節介紹了如何建立並設置Swift項目以及如何選擇和設置Swift代碼編輯器。這包括在Xcode和命令行中建立Swift項目,選擇Xcode、Visual Studio Code或AppCode作為編輯器,以及如何使用SPM安裝插件。
Thumbnail
本章節旨在為讀者提供Swift程式語言的基礎知識,包括其基本語法、註解方法和變數使用方式,並通過具體的程式碼示例來說明這些概念。這將幫助讀者理解Swift的基本結構,並學會如何在Swift中定義變數並使用註解。
Thumbnail
這份文件的目的是介紹Swift語言,包括它的特性、應用範疇,以及誰在使用它。它也提供了一些學習Swift的資源和工具,以及一些常見的Swift庫和框架。