我正在尝试使用Java 8 Stream
来查找LinkedList
中的元素。但是,我想保证过滤条件只有一个匹配。
拿这段代码:
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
(好!)
但是,如果有多个匹配项,我希望它会抛出错误。有没有办法做到这一点?
答案 0 :(得分:147)
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
List
收集器在Collectors.toList()
收集我们的对象。IllegalStateException
则抛出list.size != 1
。用作:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.collect(toSingleton());
然后,您可以根据需要自定义此Collector
,例如,在构造函数中将异常作为参数提供,调整它以允许两个值,等等。
您可以使用&#39;解决方法&#39;这涉及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);
答案 1 :(得分:91)
为了完整起见,这里是@ one-liner'对应@ 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));
答案 2 :(得分:78)
其他涉及撰写自定义Collector
的答案可能更有效(例如Louis Wasserman's,+ 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);
}
答案 3 :(得分:50)
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
。
答案 4 :(得分:36)
使用Guava的MoreCollectors.onlyElement()
(JavaDoc)。
如果流包含两个或更多元素,它会执行您想要的操作并抛出IllegalArgumentException
,如果流为空,则抛出NoSuchElementException
。
import static com.google.common.collect.MoreCollectors.onlyElement;
User match =
users.stream().filter((user) -> user.getId() < 0).collect(onlyElement());
答案 5 :(得分:28)
&#34;逃生舱&#34;允许你做流不支持的奇怪事情的操作是要求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行。
答案 6 :(得分:19)
来自@Holger的评论中的好建议:
Optional<User> match = users.stream()
.filter((user) -> user.getId() > 1)
.reduce((u, v) -> { throw new IllegalStateException("More than one ID found") });
Optional#get
引发异常,但如果您有多个元素无效。您可以收集仅接受一个项目的集合中的用户,例如:
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并获得唯一的元素。
答案 7 :(得分:11)
另一种方法是使用减少:
(此示例使用字符串,但可以轻松应用于任何对象类型,包括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();
答案 8 :(得分:5)
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
只包含一个元素。如果您已经知道这种情况,请致电:
User user = result.orElseThrow();
这会给错误的人带来负担 - 就像它应该的那样。
答案 9 :(得分:4)
Guava有一个名为MoreCollectors.onlyElement()
的Collector
。
答案 10 :(得分:3)
我认为这种方式更简单:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.findFirst().get();
答案 11 :(得分:1)
我们可以使用RxJava(非常强大的reactive extension库)
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();
如果找不到用户或多个用户,则单 operator会引发异常。
答案 12 :(得分:1)
如果您不介意使用第三方库,SequenceM
来自cyclops-streams(以及来自LazyFutureStream
的simple-react),则只需使用单个和第二方库。 singleOptional运算符。
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
披露 - 我是两个图书馆的作者。
答案 13 :(得分: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
代码是否更具可读性。
答案 14 :(得分:1)
我正在使用这两个收藏家:
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);
}
答案 15 :(得分:1)
这是我发现的更简单,更灵活的方法(基于@prunge答案)
Optional<User> user = users.stream()
.filter(user -> user.getId() == 1)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple elements: " + a + ", " + b);
})
通过这种方式获得:
Optional.empty()
(如果不存在)答案 16 :(得分:0)
我采用了直接方法并且刚刚实施了这个方法:
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<>());
}
}
此实现不线程安全。
答案 17 :(得分:0)
User match = users.stream().filter((user) -> user.getId()== 1).findAny().orElseThrow(()-> new IllegalArgumentException());
答案 18 :(得分:0)
受@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(...));
答案 19 :(得分:0)
如果您不使用番石榴或Kotlin,这是基于@skiwi和@Neuron答案的解决方案。
users.stream().collect(single(user -> user.getId() == 1));
或
users.stream().collect(optional(user -> user.getId() == 1));
其中single
和optional
是静态导入的函数,它们返回相应的收集器。
我认为,如果将过滤逻辑移到收集器内部,它将看起来更加简洁。如果您碰巧用.filter
删除了字符串,那么代码也不会中断。
代码https://gist.github.com/overpas/ccc39b75f17a1c65682c071045c1a079
的要点答案 20 :(得分:0)
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Integer value = list.stream().filter((x->x.intValue()==8)).findFirst().orElse(null);
我使用了整数类型而不是原始类型,因为它会有空指针异常。你只需要处理这个异常...看起来很简洁,我认为;)
答案 21 :(得分:-2)
你试过这个吗?
long c = users.stream().filter((user) -> user.getId() == 1).count();
if(c > 1){
throw new IllegalStateException();
}
long count()
Returns the count of elements in this stream. This is a special case of a reduction and is equivalent to:
return mapToLong(e -> 1L).sum();
This is a terminal operation.
来源:https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html