Java Optionals获得更具表现力的代码
我們中任何人使用允許空引用的語言進(jìn)行編程時,都會遇到嘗試取消引用一個引用時發(fā)生的情況。 無論是導(dǎo)致segfault還是NullPointerException,它始終是一個錯誤。 托尼·霍爾將其描述為他十億美元的錯誤 。 當(dāng)函數(shù)向客戶端的開發(fā)人員未預(yù)料到的客戶端返回空引用時,通常會發(fā)生此問題。 用這樣的代碼說:
User user = userRepository.find("Alice");一個精明的程序員會立即詢問沒有找到匹配“ Alice”的用戶,但是find()方法的簽名中沒有任何內(nèi)容告訴您期望什么。 過去,典型的Java解決方案是使該方法引發(fā)一個已檢查的異常,也許是UserNotFoundException 。 這肯定會與客戶程序員交流這種可能性的發(fā)生,但是它并不能提高他們代碼的表達(dá)力。 捕獲異常會導(dǎo)致代碼妨礙理解。 無論如何,檢查異常都不受歡迎,人們不再傾向于編寫引發(fā)異常的代碼。
許多程序員將改為拋出未檢查的異常或返回空引用。 兩者彼此一樣壞,并且出于相同的原因:它們都沒有通知程序員期望這種可能性,并且如果處理不當(dāng),它們都將導(dǎo)致運(yùn)行時失敗。 Java 8引入了Optional類型來處理此問題。 每當(dāng)編寫可能返回值或可能不返回值的方法時,都應(yīng)使該方法返回希望返回的任何類型的Optional。 因此,在上面的示例中,find將返回Optional<User>類型的值。 客戶端代碼現(xiàn)在需要執(zhí)行其他步驟來測試的存在,然后獲取值:
Optional<User> userOpt = userRepository.find("Alice"); if (userOpt.isPresent()) {User user = userOpt.get(); }而且,如果代碼不加保護(hù)地調(diào)用get()則其IDE可能會警告他們。
Lambdas使事情變得更好
該解決方案已經(jīng)好很多了,但是Optional不僅僅在于:如果您堅持以這種方式處理可選內(nèi)容,那么您將失去一些使代碼更具表現(xiàn)力的機(jī)會。
上面的代碼段改編自我自己對Codurance用于測試求職者的“社交網(wǎng)絡(luò)”練習(xí)的實現(xiàn)。 我的實際代碼更像是:
Optional<User> userOpt = userRepository.find(subject); if (userOpt.isPresent()) {User user = userOpt.get();printAllMessagesPostedToUser(user); }Optional具有ifPresent()方法,該方法允許我們提供一個Consumer ,如果存在Optional,則將調(diào)用該Consumer 。 消費(fèi)者的參數(shù)將是由可選包裝的對象。 這使我們可以像這樣重寫代碼:
userRepository.find(subject).ifPresent(user -> printAllMessagesPostedToUser(user));實際上,我們可以更進(jìn)一步,并用方法引用代替lambda:
userRepository.find(subject).ifPresent(this::printAllMessagesPostedToUser);我認(rèn)為這比if語句更清楚地傳達(dá)了程序員的意圖(在本例中為我的意圖)。
令人瘋狂的是,沒有ifNotPresent()對應(yīng)項,即使有,也沒有ifPresent是一個void方法,因此它們無論如何都無法鏈接。 Java 9通過其ifPresentOrElse(Consumer<T>, Runnable)方法來解決此問題,但是它仍然不是理想的選擇。
替換默認(rèn)值
關(guān)于不存在可選值的問題,我們該怎么辦? 如果忘記了缺少功能的抱怨, ifPresent()僅適用于具有副作用的命令。 如果要實現(xiàn)查詢,則可能需要用默認(rèn)值替換為空的可選值,例如:
if (optionalValue.isPresent()) {return optionalValue.get(); } return defaultValue;使用Optional.orElse()可以很容易地做到這一點:
return optionalValue.orElse(defaultValue);當(dāng)您必須調(diào)用可能返回null且不受您控制的方法時,這也提供了一種方便的方法,可以將值清零。 之前,我們都有所有與此類似的書面代碼:
value = methodThatMayReturnNull(); if (value == null) {value = defaultValue; }您可以使用Optional.ofNullable()重構(gòu)該代碼,因為如果該值為null,它將返回Optional.empty() :
value = Optional.ofNullable(methodThatMayReturnNull()).orElse(defaultValue);我認(rèn)為這比使用ObjectUtils.defaultIfNull做同樣的事情要好一些。 但是,有一個警告。 您不得使用Optional.orElse()來調(diào)用具有副作用的方法。 例如,在我的社交網(wǎng)絡(luò)練習(xí)的其他地方,我有搜索用戶的代碼,并在找到用戶時將其返回,否則它將創(chuàng)建一個新用戶:
Optional<User> userOpt = userRepository.find(recipient); if (userOpt.isPresent()) {return userOpt.get(); } return createUser();您可能會假設(shè)您可以像下面這樣重寫代碼:
return userRepository.find(recipient).orElse(createUser());您不必這樣做,因為無論是否存在可選參數(shù),都會始終調(diào)用createUser() ! 幾乎可以肯定這不是您想要的:充其量您將進(jìn)行不必要的方法調(diào)用,并且,如果該方法有副作用,則可能會引入錯誤。 相反,您應(yīng)該調(diào)用Optional.orElseGet()并為它提供一個提供默認(rèn)值的Supplier :
return userRepository.find(recipient).orElseGet(() -> createUser());現(xiàn)在,僅當(dāng)不存在可選參數(shù)時才調(diào)用createUser() ,這是我想要的行為。 再一次,我們可以將lambda替換為方法參考:
return userRepository.find(recipient).orElseGet(this::createUser);拋出異常
對于您來說,當(dāng)可選項不存在并且您想引發(fā)異常時,可能是錯誤情況。 您可以通過調(diào)用Optional.orElseThrow()并將其傳遞給創(chuàng)建異常的Supplier來實現(xiàn):
return userRepository.find(recipient).orElseThrow(() -> new RuntimeException("User " + recipient + " not found"));映射可選值
Optional還具有一些方法,使您可以執(zhí)行類似于流上的操作。 例如,在另一個練習(xí)中,我有一些結(jié)構(gòu)類似于此的代碼:
Optional<Amount> creditAmountOpt = transaction.getCreditAmount(); Optional<Amount> debitAmountOpt = transaction.getDebitAmount();String formattedDepositAmount = creditAmountOpt.isPresent() ?formatAmount(creditAmountOpt.get()) : " ";String formattedWithdrawalAmount = debitAmountOpt.isPresent() ?formatAmount(debitAmountOpt.get()) : " ";return String.format(" %s| %s|", formattedDepositAmount, formattedWithdrawalAmount);該代碼的上下文是一個打印銀行對帳單行的類:我的Transaction類知道它是存款還是提款,但我不希望對帳單行打印機(jī)知道。 因此,我讓Transaction接口返回了借方和貸方金額的可選值:如果行存在,對帳單行打印機(jī)將格式化每個值,否則將替換為空白。
為了避免條件運(yùn)算符,我們可以使用Optional.map()方法。 這與Stream API上的map方法非常相似。 它接受一個Function并在存在可選選項時調(diào)用它。 它將包裝的值作為函數(shù)參數(shù)傳遞,并將返回值包裝在另一個Optional中。 因此,在這種情況下,它將Optional<Amount>映射到Optional<String> 。 這使我們可以像這樣重寫代碼:
return String.format(" %s| %s|",transaction.getDepositAmount().map(this::formatAmount).orElse(" "),transaction.getWithdrawalAmount().map(this::formatAmount).orElse(" "));您可能想知道,如果映射一個返回另一個可選參數(shù)的函數(shù),即Function<T, Optional<U>> ,在這種情況下,您最終得到的是Optional<Optional<U>>類型的結(jié)果,可能不是你要。 同樣,類似于流,您可以改用flatMap()來返回Optional<U>值。
與流的相似性擴(kuò)展到Optional.filter() ,如果存在可選值,則評估提供的謂詞,當(dāng)謂詞評估為false時,它將返回一個空的可選值。 明智的是,避免變得過于可愛,如果不加小心,您可能會得到難以理解的代碼。 可選參數(shù)最適合用于重構(gòu)簡單明了的代碼,但將其長期保存為簡單明了的代碼。
但小心點
最后,任何有用的工具都可能被濫用,因此Optional也是如此。 它們僅用于表示返回值。 如果您聲明類型為Optional的實例變量,則IntelliJ會警告您。 這構(gòu)成了一個臨時字段的顯式聲明,該字段被視為代碼氣味 。 另外,不要將Optionals用作方法參數(shù):本質(zhì)上,這是偽裝的布爾型參數(shù),也被認(rèn)為很臭。 如果發(fā)現(xiàn)自己想要這樣做,最好將方法分為兩個方法:一個帶有參數(shù),另一個不帶參數(shù),然后將條件放在客戶端代碼中。
翻譯自: https://www.javacodegeeks.com/2017/11/java-optionals-expressive-code.html
總結(jié)
以上是生活随笔為你收集整理的Java Optionals获得更具表现力的代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大脑控制电脑(大脑控制电脑的软件)
- 下一篇: 将测微仪与Spring Boot 2一起