Java 8 Stream
を使って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;
}
}
このコードは、それらのIDに基づいてUser
を見つけます。しかし、何個のUser
がフィルタにマッチしたかについての保証はありません。
フィルタ行を次のように変更します。
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
NoSuchElementException
をスローします(いいね!)
ただし、複数の一致がある場合はエラーをスローします。これを行う方法はありますか?
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
を作成します。
Collectors.toList()
コレクターを使ってList
にオブジェクトを集める。list.size != 1
の場合はIllegalStateException
がスローされます。使用されます:
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);
完全を期すために、@ 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));
カスタム 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);
}
Guava はここで正しいことをする MoreCollectors.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
を再利用することができます。
Guavaの MoreCollectors.onlyElement()
( JavaDoc )を使ってください。
ストリームが2つ以上の要素で構成されている場合はIllegalArgumentException
をスローし、ストリームが空の場合はNoSuchElementException
をスローします。
import static com.google.common.collect.MoreCollectors.onlyElement;
User match =
users.stream().filter((user) -> user.getId() < 0).collect(onlyElement());
ストリームではサポートされていない奇妙なことをできるようにする "escape hatch"操作は、Iterator
を要求することです。
Iterator<T> it = users.stream().filter((user) -> user.getId() < 0).iterator();
if (!it.hasNext())
throw new NoSuchElementException();
else {
result = it.next();
if (it.hasNext())
throw new TooManyElementsException();
}
GuavaはIterator
を取り、唯一の要素を取得する便利な方法を持っています。ゼロまたは複数の要素がある場合はスローします。これはここでは下のn-1行を置き換えることができます。
@ 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();
この減少は本質的に次のようになります。
結果はオプションにラップされます。
しかし最も簡単な解決策は、おそらくコレクションに集め、そのサイズが1であることを確認し、唯一の要素を取得することです。
(この例では文字列を使用していますが、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();
Collector
を使用します。public static <T> Collector<T, ?, Optional<T>> toSingleton() {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty()
);
}
Optional<User> result = users.stream()
.filter((user) -> user.getId() < 0)
.collect(toSingleton());
Optional
を返すのは、通常、Collection
が正確に1つの要素を含むと仮定することはできないからです。これが事実であることを既に知っているならば、呼び出してください:
User user = result.orElseThrow();
そうすることで、これは呼び出し側にエラーを煩わすという負担をかけます。
Guava には MoreCollectors.onlyElement()
と呼ばれるCollector
があります。
サードパーティのライブラリを使用しても構わない場合は、 cyclops-streams から SequenceM
(および LazyFutureStream
from simple-react )は両方ともsingle&singleOptional演算子を持ちます。
Stream
内に0
個または1
個を超える要素がある場合、singleOptional()
は例外をスローします。それ以外の場合は単一の値を返します。
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();
Stream
に値がない場合、または複数の値がある場合、singleOptional()
はOptional.empty()
を返します。
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
開示 - 私は両方の図書館の著者です。
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
を使用してさらに読みやすくならないかどうかはわかりません。
これは私が見つけたより簡単で柔軟な方法です(@prungeの回答に基づく)
Optional<User> user = users.stream()
.filter(user -> user.getId() == 1)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple elements: " + a + ", " + b);
})
このようにあなたが得る:
Optional.empty()
私はこれら二つのコレクターを使っています:
public static <T> Collector<T, ?, Optional<T>> zeroOrOne() {
return Collectors.reducing((a, b) -> {
throw new IllegalStateException("More than one value was returned");
});
}
public static <T> Collector<T, ?, T> onlyOne() {
return Collectors.collectingAndThen(zeroOrOne(), Optional::get);
}
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();
単一演算子 は、ユーザーが見つからない場合、または1人以上のユーザーが見つかった場合に例外をスローします。
私は直接アプローチで行って、ただ事を実行しました:
public class CollectSingle<T> implements Collector<T, T, T>, BiConsumer<T, T>, Function<T, T>, Supplier<T> {
T value;
@Override
public Supplier<T> supplier() {
return this;
}
@Override
public BiConsumer<T, T> accumulator() {
return this;
}
@Override
public BinaryOperator<T> combiner() {
return null;
}
@Override
public Function<T, T> finisher() {
return this;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
@Override //accumulator
public void accept(T ignore, T nvalue) {
if (value != null) {
throw new UnsupportedOperationException("Collect single only supports single element, "
+ value + " and " + nvalue + " found.");
}
value = nvalue;
}
@Override //supplier
public T get() {
value = null; //reset for reuse
return value;
}
@Override //finisher
public T apply(T t) {
return value;
}
}
jUnitテストでは:
public class CollectSingleTest {
@Test
public void collectOne( ) {
List<Integer> lst = new ArrayList<>();
lst.add(7);
Integer o = lst.stream().collect( new CollectSingle<>());
System.out.println(o);
}
@Test(expected = UnsupportedOperationException.class)
public void failOnTwo( ) {
List<Integer> lst = new ArrayList<>();
lst.add(7);
lst.add(8);
Integer o = lst.stream().collect( new CollectSingle<>());
}
}
この実装はスレッドセーフではありません。