述語で最初の要素を見つける


504

私はJava 8ラムダで遊んだばかりで、関数型言語で慣れ親しんでいることのいくつかを実装しようとしています。

たとえば、ほとんどの関数型言語には、シーケンス、または述語がである最初の要素を返すリストを操作する何らかの検索関数がありtrueます。Java 8でこれを達成するために私が見ることができる唯一の方法は、次のとおりです。

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

ただし、フィルターはリスト全体をスキャンするため、これは私には非効率的です。少なくとも私の理解では(これは間違っている可能性があります)。もっと良い方法はありますか?


53
非効率ではありません。Java8 Streamの実装は遅延評価されるため、フィルターは端末操作にのみ適用されます。ここで同じ質問:stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
Marek Gregor

1
涼しい。それが私が望んでいたことです。それ以外の場合、それは主要な設計の失敗でした。
siki 2014年

2
リストにそのような要素が含まれるかどうかを確認することを本当に意図している場合は(おそらくいくつかの最初のものを除外するのではなく)、. findAny()は理論的には並列設定でより効率的であり、もちろんその意図をより明確に伝えます。
Joachim Lous

単純なforEachサイクルと比較すると、ヒープ上に多数のオブジェクトが作成され、数十の動的メソッド呼び出しが作成されます。これは必ずしもパフォーマンステストの最終結果に影響を与えるとは限りませんが、ホットスポットでは、Streamや同様のヘビーウェイトコンストラクトの些細な使用を控えることに違いがあります。
Agoston Horvath

回答:


720

いいえ、フィルターはストリーム全体をスキャンしません。これは、レイジーストリームを返す中間操作です(実際には、すべての中間操作がレイジーストリームを返します)。納得させるには、次のテストを実行するだけです。

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

ストリームの最初の2つの要素のみが実際に処理されていることがわかります。

だからあなたは完全に素晴らしいあなたのアプローチで行くことができます。


37
注として、get();ストリームパイプラインにフィードする値がわかっているため、ここで使用しました。これにより、結果が得られます。ストリームパイプラインに適用された操作が要素になるかどうかわからない場合get();があるため、実際にはorElse()/ ではなく、/ orElseGet()/ orElseThrow()(NSEEではなくより意味のあるエラーの場合)を使用する必要があります。
Alexis

31
.findFirst().orElse(null);
Gondy、

20
orElse nullは使用しないでください。それはアンチパターンであるべきです。それはすべてオプションに含まれているので、なぜNPEを危険にさらす必要があるのですか?私はオプションを扱う方が良い方法だと思います。使用する前にisPresent()でオプションをテストしてください。
BeJay

@BeJayわかりません。代わりに何を使うべきorElseですか?
ジョンヘンケル

3
@JohnHenckel BeJayが意味することは、それをOptionalタイプとして残すべきだということだと思い.findFirstます。Optionalの使用方法の1つは、開発者nullがチェックmyObject != nullではなくseg を処理する必要をなくすことを支援することmyOptional.isPresent()です。オプションインターフェイスのその他の部分をチェックまたは使用できます。それはそれをより明確にしましたか?
AMTerp

102

しかし、フィルターはリスト全体をスキャンするため、これは私には非効率的です

いいえ、そうではありません-述語を満たす最初の要素が見つかるとすぐに「壊れます」。遅延の詳細については、特にjavadocストリームパッケージを参照してください(強調は私のものです)。

フィルタリング、マッピング、重複除去などの多くのストリーム操作は遅延して実装でき、最適化の機会を提供します。たとえば、「3つの連続した母音で最初の文字列を見つける」では、すべての入力文字列を調べる必要はありません。ストリーム操作は、中間(ストリーム生成)操作と最終(値生成または副作用生成)操作に分かれています。中間操作は常に遅延します。


5
この回答は私にとってより有益であり、理由だけでなく方法についても説明しています。新しい中間操作が常に遅延することはありません。Javaストリームは私を驚かせ続けています。
kevinarpe 2015

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

オブジェクトのリストから1つのオブジェクトのみを除外する必要がありました。だから私はこれを使いました、それが役に立てば幸いです。


より良い:ブール値の戻り値を探しているので、nullチェックを追加することでより適切に実行できます:return dataSource.getParkingLots()。stream()。filter(parkingLot-> Objects.equals(parkingLot.getId()、id)) .findFirst()。orElse(null)!= null;
shreedhar bhat

1
@shreedharbhatする必要はありません.orElse(null) != null。代わりに、オプションAPIの.isPresentie を使用してください.findFirst().isPresent()
AMTerp

@shreedharbhatはまず、OPがブール値の戻り値を探していませんでした。第二に、もしそうなら、それは書く方がきれいだろう.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
AjaxLeung

13

Alexis Cの回答に加えて、検索している要素が存在するかどうかわからない配列リストを使用している場合は、これを使用してください。

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

次に、aがであるかどうかを確認しますnull


1
例を修正する必要があります。プレーンなintにnullを割り当てることはできません。stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
RubioRic

あなたの投稿を編集しました。整数のリストで検索している場合、0(ゼロ)は有効な結果である可能性があります。変数タイプを整数に、デフォルト値をnullに置き換えました。
RubioRic 2018

0

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);
        }
    }
}


0

改良された1行回答:ブール値の戻り値を探している場合は、isPresentを追加することでより適切に実行できます。

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

ブール値の戻り値が必要な場合は、anyMatch
AjaxLeung
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.