簡體   English   中英

通過謂詞查找第一個元素

[英]Find first element by predicate

我剛剛開始使用 Java 8 lambdas,我正在嘗試實現一些我在函數式語言中習慣的東西。

例如,大多數函數式語言都有某種對序列或返回第一個元素的列表進行操作的 find 函數,其中謂詞為true 我可以看到在 Java 8 中實現這一目標的唯一方法是:

lst.stream()
    .filter(x -> x > 5)
    .findFirst()

然而,這對我來說似乎效率低下,因為過濾器將掃描整個列表,至少在我的理解中(這可能是錯誤的)。 有沒有更好的辦法?

不,過濾器不會掃描整個流。 它是一個中間操作,它返回一個惰性流(實際上所有的中間操作都返回一個惰性流)。 為了說服您,您可以簡單地進行以下測試:

List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("will filter " + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

哪些輸出:

will filter 1
will filter 10
10

您會看到實際上只處理了流的前兩個元素。

所以你可以采用你的方法,這是非常好的。

然而,這對我來說似乎效率低下,因為過濾器將掃描整個列表

不,它不會——只要找到滿足謂詞的第一個元素,它就會“中斷”。 您可以在流包 javadoc 中閱讀更多關於懶惰的信息,特別是(強調我的):

許多流操作,例如過濾、映射或重復刪除,可以懶惰地實現,從而暴露優化機會。 例如,“找到具有三個連續元音的第一個字符串”不需要檢查所有輸入字符串。 流操作分為中間(Stream-生產)操作和終端(價值或副作用生產)操作。 中間操作總是懶惰的。

return dataSource.getParkingLots()
                 .stream()
                 .filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
                 .findFirst()
                 .orElse(null);

我只需要從對象列表中過濾掉一個對象。 所以我用了這個,希望它有幫助。

除了Alexis C的回答之外,如果您正在使用數組列表,並且不確定您要搜索的元素是否存在,請使用它。

Integer a = list.stream()
                .peek(num -> System.out.println("will filter " + num))
                .filter(x -> x > 5)
                .findFirst()
                .orElse(null);

然后你可以簡單地檢查a是否為null

已經由@AjaxLeung 回答,但在評論中很難找到。
僅供檢查

lst.stream()
    .filter(x -> x > 5)
    .findFirst()
    .isPresent()

簡化為

lst.stream()
    .anyMatch(x -> x > 5)

改進的 One-Liner 答案:如果您正在尋找布爾返回值,我們可以通過添加 isPresent 來做得更好:

return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();

import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

// Stream is ~30 times slower for same operation...
public class StreamPerfTest {

    int iterations = 100;
    List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);


    // 55 ms
    @Test
    public void stream() {

        for (int i = 0; i < iterations; i++) {
            Optional<Integer> result = list.stream()
                    .filter(x -> x > 5)
                    .findFirst();

            System.out.println(result.orElse(null));
        }
    }

    // 2 ms
    @Test
    public void loop() {

        for (int i = 0; i < iterations; i++) {
            Integer result = null;
            for (Integer walk : list) {
                if (walk > 5) {
                    result = walk;
                    break;
                }
            }
            System.out.println(result);
        }
    }
}

除非您的列表確實很大 (成千上萬個元素),否則在此處使用流非常昂貴,甚至會使代碼難以理解。

注意:java不是一種功能語言(而jvm並不特別適合有效地實現功能語言)。

(在所有Iterable上)更簡單,更有效:

for (MyType walk : lst)
    if (walk > 5) { do_whatever; break; }

或者,如果您想跳過迭代器:

for (int x=0; x<list.size(); x++)
    if (list.get(x) > 5 { do_whatever; break; }

實際上,我真的很奇怪,為什么有人建議使用這種復雜而昂貴的流機制,即使對於諸如獲取數組的第一個元素這樣的瑣碎事情也是如此。 (是的:Java8仍然支持數組)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM