当心findFirst()和findAny()
過濾Java 8 Stream ,通常使用findFirst()或findAny()來獲取在過濾器中幸存的元素。 但這可能并不能真正實現(xiàn)您的意思,并且可能會出現(xiàn)一些細微的錯誤。
那么
從我們的Javadoc( 此處和此處 )可以看出,這兩個方法都從流中返回任意元素-除非流具有遇到順序 ,在這種情況下, findFirst()返回第一個元素。 簡單。
一個簡單的示例如下所示:
public Optional<Customer> findCustomer(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).findFirst(); }當然,這只是舊的for-each-loop的漂亮版本:
public Optional<Customer> findCustomer(String customerId) {for (Customer customer : customers)if (customer.getId().equals(customerId))return Optional.of(customer);return Optional.empty(); }但是,這兩種變體都包含相同的潛在錯誤:它們是基于隱含的假設而建立的,即只有一個具有任何給定ID的客戶。
現(xiàn)在,這可能是一個非常合理的假設。 也許這是一個已知的不變式,由系統(tǒng)的專用部分保護,并由其他人員依賴。 在那種情況下,這是完全可以的。
通常,代碼依賴于唯一的匹配元素,但是沒有做任何斷言。
但是,在許多情況下,我并不是在野外看到的。 也許客戶只是從外部來源加載的,這些來源無法保證其ID的唯一性。 也許現(xiàn)有的錯誤允許兩本書具有相同的ISBN。 也許搜索詞允許出乎意料的許多意外匹配(有人說過正則表達式嗎?)。
通常,代碼的正確性取決于以下假設:存在與條件匹配的唯一元素,但它不執(zhí)行或聲明此條件。
更糟糕的是,不當行為完全是由數(shù)據(jù)驅動的,可能會在測試期間將其隱藏。 除非我們牢記這種情況,否則我們可能會完全忽略它,直到它在生產中顯現(xiàn)出來為止。
更糟糕的是,它默默地失敗了! 如果只有一個這樣的元素的假設被證明是錯誤的,我們將不會直接注意到這一點。 取而代之的是,系統(tǒng)會在觀察到影響并查明原因之前巧妙地表現(xiàn)出一段時間。
因此,當然, findFirst()和findAny()本身并沒有錯。 但是,使用它們很容易導致建模域邏輯中的錯誤。
Steven Depolo在CC-BY 2.0下發(fā)布
快速失敗
因此,讓我們解決這個問題! 假設我們非常確定最多有一個匹配元素,如果沒有,我們希望代碼快速失敗 。 通過循環(huán),我們必須管理一些難看的狀態(tài),它看起來如下:
public Optional<Customer> findOnlyCustomer(String customerId) {boolean foundCustomer = false;Customer resultCustomer = null;for (Customer customer : customers)if (customer.getId().equals(customerId))if (!foundCustomer) {foundCustomer = true;resultCustomer = customer;} else {throw new DuplicateCustomerException();}return foundCustomer? Optional.of(resultCustomer): Optional.empty(); }現(xiàn)在,流為我們提供了一種更好的方法。 我們可以使用經(jīng)常被忽略的reduce, 文檔中對此說 :
使用關聯(lián)累加函數(shù)對此流的元素進行歸約 ,并返回一個Optional描述歸約值(如果有)。 這等效于:
流減少
boolean foundAny = false; T result = null; for (T element : this stream) {if (!foundAny) {foundAny = true;result = element;}elseresult = accumulator.apply(result, element); } return foundAny ? Optional.of(result) : Optional.empty();但不限于順序執(zhí)行。
看起來不像上面的循環(huán)嗎?! 瘋狂的巧合...
因此,我們需要的是一個累加器,該累加器會在調用后立即拋出所需的異常:
public Optional<Customer> findOnlyCustomerWithId_manualException(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).reduce((element, otherElement) -> {throw new DuplicateCustomerException();}); }這看起來有些奇怪,但確實可以滿足我們的要求。 為了使其更具可讀性,我們應該將其放入Stream實用工具類中,并為其命名一個漂亮的名稱:
public static <T> BinaryOperator<T> toOnlyElement() {return toOnlyElementThrowing(IllegalArgumentException::new); }public static <T, E extends RuntimeException> BinaryOperator<T> toOnlyElementThrowing(Supplier<E> exception) {return (element, otherElement) -> {throw exception.get();}; }現(xiàn)在我們可以這樣稱呼它:
// if a generic exception is fine public Optional<Customer> findOnlyCustomer(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).reduce(toOnlyElement()); }// if we want a specific exception public Optional<Customer> findOnlyCustomer(String customerId) {return customers.stream().filter(customer -> customer.getId().equals(customerId)).reduce(toOnlyElementThrowing(DuplicateCustomerException::new)); }目的顯示代碼如何?
這將實現(xiàn)整個流。
應該注意的是,與findFirst()和findAny() ,這當然不是短路操作 ,它將實現(xiàn)整個流。 也就是說,如果確實只有一個元素。 當然,一旦遇到第二個元素,處理就會停止。
反射
我們已經(jīng)看到findFirst()和findAny()如何不足以表示流中最多剩余一個元素的假設。 如果我們要表達該假設,并確保在違反代碼時代碼快速失敗,則需要reduce(toOnlyElement()) 。
- 您可以在GitHub上找到代碼并隨意使用-它在公共領域。
首先感謝Boris Terzic使我意識到這種意圖不匹配。
翻譯自: https://www.javacodegeeks.com/2016/02/beware-findfirst-findany.html
總結
以上是生活随笔為你收集整理的当心findFirst()和findAny()的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 映客直播安卓最新版本(映客直播安卓)
- 下一篇: 深入理解linux内核pdf(linux