287

Java 8 を使用Streamして a 内の要素を見つけようとしていLinkedListます。ただし、フィルター条件に一致するものが 1 つだけであることを保証したいと思います。

次のコードを使用します。

public static void main(String[] args) {

    LinkedList<User> users = new LinkedList<>();
    users.add(new User(1, "User1"));
    users.add(new User(2, "User2"));
    users.add(new User(3, "User3"));

    User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
    System.out.println(match.toString());
}

static class User {

    @Override
    public String toString() {
        return id + " - " + username;
    }

    int id;
    String username;

    public User() {
    }

    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public int getId() {
        return id;
    }
}

Userこのコードは、 ID に基づいて を見つけます。Userただし、フィルタに一致した の数は保証されません。

フィルター行を次のように変更します。

User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();

NoSuchElementException(良い!)を投げます

ただし、複数の一致がある場合はエラーをスローしたいと思います。これを行う方法はありますか?

4

24 に答える 24

253

カスタムを作成するCollector

public static <T> Collector<T, ?, T> toSingleton() {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                if (list.size() != 1) {
                    throw new IllegalStateException();
                }
                return list.get(0);
            }
    );
}

私たちは、私たちの希望をCollectors.collectingAndThen構築するために使用しますCollector

  1. コレクターでオブジェクトをList収集しCollectors.toList()ます。
  2. 最後に追加のフィニッシャーを適用すると、単一の要素が返されるか、IllegalStateExceptionifがスローされlist.size != 1ます。

使用されます:

User resultUser = users.stream()
        .filter(user -> user.getId() > 0)
        .collect(toSingleton());

その後、必要に応じてこれをカスタマイズできCollectorます。たとえば、例外をコンストラクターの引数として指定したり、2 つの値を許可するように調整したりできます。

別の — 間違いなくエレガントではない — 解決策:

peek()とを含む「回避策」を使用できますAtomicIntegerが、実際には使用しないでください。

List代わりにできることは、次のように で収集することです。

LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
List<User> resultUserList = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.toList());
if (resultUserList.size() != 1) {
    throw new IllegalStateException();
}
User resultUser = resultUserList.get(0);
于 2014-03-27T17:33:03.767 に答える
150

完全を期すために、@prungeの優れた回答に対応する「ワンライナー」を次に示します。

User user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .reduce((a, b) -> {
            throw new IllegalStateException("Multiple elements: " + a + ", " + b);
        })
        .get();

これは、ストリームから唯一の一致する要素を取得し、スローします

  • NoSuchElementExceptionストリームが空の場合、または
  • IllegalStateExceptionストリームに一致する要素が複数含まれている場合。

このアプローチのバリエーションは、例外を早期にスローすることを回避し、代わりにOptional、単一の要素を含むか、ゼロまたは複数の要素がある場合は何も含まない (空)として結果を表します。

Optional<User> user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.reducing((a, b) -> null));
于 2016-01-07T19:51:39.967 に答える
90

カスタムの記述を伴う他の回答Collectorはおそらくより効率的です ( Louis Wasserman の, +1 など) が、簡潔にしたい場合は、次のことをお勧めします。

List<User> result = users.stream()
    .filter(user -> user.getId() == 1)
    .limit(2)
    .collect(Collectors.toList());

次に、結果リストのサイズを確認します。

if (result.size() != 1) {
  throw new IllegalStateException("Expected exactly one user but got " + result);
User user = result.get(0);
}
于 2014-03-28T15:39:23.007 に答える
85

GuavaMoreCollectors.onlyElement()は、ここで正しいことを行うものを提供します。ただし、自分で行う必要がある場合は、自分でロールバックできますCollector

<E> Collector<E, ?, Optional<E>> getOnly() {
  return Collector.of(
    AtomicReference::new,
    (ref, e) -> {
      if (!ref.compareAndSet(null, e)) {
         throw new IllegalArgumentException("Multiple values");
      }
    },
    (ref1, ref2) -> {
      if (ref1.get() == null) {
        return ref2;
      } else if (ref2.get() != null) {
        throw new IllegalArgumentException("Multiple values");
      } else {
        return ref1;
      }
    },
    ref -> Optional.ofNullable(ref.get()),
    Collector.Characteristics.UNORDERED);
}

...または のHolder代わりに独自の型を使用しますAtomicReference。好きなだけ再利用できCollectorます。

于 2014-03-27T17:51:16.513 に答える
26

アップデート

@Holger からのコメントでの素敵な提案:

Optional<User> match = users.stream()
              .filter((user) -> user.getId() > 1)
              .reduce((u, v) -> { throw new IllegalStateException("More than one ID found") });

元の答え

によって例外がスローされOptional#getますが、要素が複数ある場合は役に立ちません。たとえば、次のように、1 つのアイテムのみを受け入れるコレクションにユーザーを収集できます。

User match = users.stream().filter((user) -> user.getId() > 1)
                  .collect(toCollection(() -> new ArrayBlockingQueue<User>(1)))
                  .poll();

をスローしますjava.lang.IllegalStateException: Queue fullが、それはハックしすぎます。

または、オプションと組み合わせて削減を使用できます。

User match = Optional.ofNullable(users.stream().filter((user) -> user.getId() > 1)
                .reduce(null, (u, v) -> {
                    if (u != null && v != null)
                        throw new IllegalStateException("More than one ID found");
                    else return u == null ? v : u;
                })).get();

リダクションは基本的に以下を返します。

  • ユーザーが見つからない場合は null
  • ユーザーが 1 つだけ見つかった場合
  • 複数見つかった場合は例外をスローします

結果はオプションでラップされます。

しかし、最も簡単な解決策は、おそらくコレクションに収集し、そのサイズが 1 であることを確認して、唯一の要素を取得することです。

于 2014-03-27T17:37:16.047 に答える
17

別の方法として、リダクションを使用することもできます (この例では文字列を使用していますが、 を含む任意のオブジェクト タイプに簡単に適用できますUser) 。

List<String> list = ImmutableList.of("one", "two", "three", "four", "five", "two");
String match = list.stream().filter("two"::equals).reduce(thereCanBeOnlyOne()).get();
//throws NoSuchElementException if there are no matching elements - "zero"
//throws RuntimeException if duplicates are found - "two"
//otherwise returns the match - "one"
...

//Reduction operator that throws RuntimeException if there are duplicates
private static <T> BinaryOperator<T> thereCanBeOnlyOne()
{
    return (a, b) -> {throw new RuntimeException("Duplicate elements found: " + a + " and " + b);};
}

したがって、Userあなたの場合は次のようになります。

User match = users.stream().filter((user) -> user.getId() < 0).reduce(thereCanBeOnlyOne()).get();
于 2015-05-13T02:01:28.927 に答える
2

RxJava (非常に強力なリアクティブ拡張ライブラリ)を使用できます

LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));

User userFound =  Observable.from(users)
                  .filter((user) -> user.getId() == 1)
                  .single().toBlocking().first();

ユーザーが見つからない場合、または複数のユーザーが見つかった場合、single オペレーターは例外をスローします

于 2016-01-07T21:40:10.073 に答える
1

スローイングマージを使用して同じCollectors.toMap(keyMapper, valueMapper)キーを持つ複数のエントリを処理するのは簡単です:

List<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));

int id = 1;
User match = Optional.ofNullable(users.stream()
  .filter(user -> user.getId() == id)
  .collect(Collectors.toMap(User::getId, Function.identity()))
  .get(id)).get();

IllegalStateException重複キーの を取得します。しかし、最後に、if.

于 2016-09-08T07:52:12.510 に答える
1

サードパーティのライブラリを使用してもかまわない場合は、SequenceMcyclops -streams (およびsimple-react ) の両方に single および singleOptional 演算子があります。LazyFutureStream

singleOptional()0にまたはそれ以上の1要素がある場合は例外をスローしStream、それ以外の場合は単一の値を返します。

String result = SequenceM.of("x")
                          .single();

SequenceM.of().single(); // NoSuchElementException

SequenceM.of(1, 2, 3).single(); // NoSuchElementException

String result = LazyFutureStream.fromStream(Stream.of("x"))
                          .single();

singleOptional()Optional.empty()に値がないか、複数の値がある場合は を返しますStream

Optional<String> result = SequenceM.fromStream(Stream.of("x"))
                          .singleOptional(); 
//Optional["x"]

Optional<String> result = SequenceM.of().singleOptional(); 
// Optional.empty

Optional<String> result =  SequenceM.of(1, 2, 3).singleOptional(); 
// Optional.empty

開示 - 私は両方のライブラリの作成者です。

于 2016-01-11T06:08:30.297 に答える
-1

@skiwi に触発されて、次の方法で解決しました。

public static <T> T toSingleton(Stream<T> stream) {
    List<T> list = stream.limit(1).collect(Collectors.toList());
    if (list.isEmpty()) {
        return null;
    } else {
        return list.get(0);
    }
}

その後:

User user = toSingleton(users.stream().filter(...).map(...));
于 2020-09-12T19:51:23.780 に答える