findfirst_当心findFirst()和findAny()
findfirst
過濾Java 8 Stream ,通常使用findFirst()或findAny()來獲取在過濾器中幸存的元素。 但這可能并不能真正實現您的意思,并且可能會出現一些細微的錯誤。
那么
從我們的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的客戶。
現在,這可能是一個非常合理的假設。 也許這是一個已知的不變式,由系統的專用部分保護,并由其他人員依賴。 在這種情況下,這完全可以。
通常,代碼依賴于唯一的匹配元素,但沒有做任何斷言。
但是在很多情況下,我不是在野外看到的。 也許客戶只是從外部來源加載的,這些來源無法保證其ID的唯一性。 也許現有的錯誤允許兩本書具有相同的ISBN。 也許搜索詞允許出乎意料的許多意外匹配(有人說過正則表達式嗎?)。
通常,代碼的正確性取決于以下假設:存在與條件匹配的唯一元素,但它不執行或斷言該元素。
更糟糕的是,不當行為完全是由數據驅動的,可能會在測試期間將其隱藏。 除非考慮到這種情況,否則我們可能會完全忽略它,直到它在生產中出現為止。
更糟糕的是,它默默地失敗了! 如果只有一個這樣的元素的假設被證明是錯誤的,我們將不會直接注意到這一點。 取而代之的是,系統會在觀察到影響并查明原因之前微妙地運行一段時間。
因此,當然, findFirst()和findAny()本質上沒有錯。 但是,使用它們很容易導致建模域邏輯中的錯誤。
Steven Depolo在CC-BY 2.0下發布
快速失敗
因此,讓我們解決這個問題! 假設我們非常確定最多有一個匹配元素,如果沒有,我們希望代碼快速失敗 。 通過循環,我們必須管理一些難看的狀態,它看起來如下:
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(); }現在,流為我們提供了更好的方法。 我們可以使用經常被忽略的reduce, 文檔中對此說 :
執行減少有關此流的元件,使用締合累積功能,并返回一個可選描述的縮小值,如果有的話。 這等效于:
流減少
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();但不限于順序執行。
看起來不像上面的循環嗎?! 瘋狂的巧合...
因此,我們需要的是一個累加器,該累加器會在調用后立即拋出所需的異常:
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();}; }現在我們可以這樣稱呼它:
// 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)); }目的顯示代碼如何?
這將實現整個流。
應該注意的是,與findFirst()和findAny() ,這當然不是短路操作 ,它將實現整個流。 也就是說,如果確實只有一個元素。 當然,一旦遇到第二個元素,處理就會停止。
反射
我們已經看到findFirst()和findAny()如何不足以表示流中最多剩余一個元素的假設。 如果我們要表達該假設,并確保在違反該代碼時快速失敗,則需要reduce(toOnlyElement()) 。
- 您可以在GitHub上找到代碼并隨意使用-它在公共領域。
首先感謝Boris Terzic使我意識到這種意圖不匹配。
翻譯自: https://www.javacodegeeks.com/2016/02/beware-findfirst-findany.html
findfirst
總結
以上是生活随笔為你收集整理的findfirst_当心findFirst()和findAny()的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简单工厂抽象工厂工厂方法_让工厂美丽
- 下一篇: 救援车备案后受限行影响吗?(救援车备案)