将 Java Stream 过滤为 1 个且只有 1 个元素
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22694884/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Filter Java Stream to 1 and only 1 element
提问by ryvantage
I am trying to use Java 8 Stream
s to find elements in a LinkedList
. I want to guarantee, however, that there is one and only one match to the filter criteria.
我正在尝试使用 Java 8 Stream
s 在LinkedList
. 但是,我想保证只有一个匹配过滤条件。
Take this code:
拿这个代码:
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;
}
}
This code finds a User
based on their ID. But there are no guarantees how many User
s matched the filter.
此代码User
根据他们的 ID找到一个。但是不能保证有多少User
s 与过滤器匹配。
Changing the filter line to:
将过滤器线更改为:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Will throw a NoSuchElementException
(good!)
会抛出一个NoSuchElementException
(好!)
I would like it to throw an error if there are multiple matches, though. Is there a way to do this?
但是,如果有多个匹配项,我希望它抛出错误。有没有办法做到这一点?
采纳答案by skiwi
Create a custom Collector
创建自定义 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);
}
);
}
We use Collectors.collectingAndThen
to construct our desired Collector
by
我们Collectors.collectingAndThen
用来构建我们想要Collector
的
- Collecting our objects in a
List
with theCollectors.toList()
collector. - Applying an extra finisher at the end, that returns the single element — or throws an
IllegalStateException
iflist.size != 1
.
List
使用Collectors.toList()
收集器收集我们的对象。- 在最后应用一个额外的完成器,返回单个元素 - 或抛出一个
IllegalStateException
iflist.size != 1
。
Used as:
用作:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.collect(toSingleton());
You can then customize this Collector
as much as you want, for example give the exception as argument in the constructor, tweak it to allow two values, and more.
然后,您可以根据需要对其进行自定义Collector
,例如在构造函数中将异常作为参数,调整它以允许两个值等等。
An alternative — arguably less elegant — solution:
另一种 - 可以说不太优雅 - 解决方案:
You can use a 'workaround' that involves peek()
and an AtomicInteger
, but really you shouldn't be using that.
您可以使用涉及peek()
和 的“解决方法” AtomicInteger
,但实际上您不应该使用它。
What you could do istead is just collecting it in a List
, like this:
你可以做的只是将它收集在 a 中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);
回答by assylias
Update
更新
Nice suggestion in comment from @Holger:
@Holger 评论中的好建议:
Optional<User> match = users.stream()
.filter((user) -> user.getId() > 1)
.reduce((u, v) -> { throw new IllegalStateException("More than one ID found") });
Original answer
原答案
The exception is thrown by Optional#get
, but if you have more than one element that won't help. You could collect the users in a collection that only accepts one item, for example:
异常由 抛出Optional#get
,但如果您有多个无用的元素。您可以将用户收集到只接受一项的集合中,例如:
User match = users.stream().filter((user) -> user.getId() > 1)
.collect(toCollection(() -> new ArrayBlockingQueue<User>(1)))
.poll();
which throws a java.lang.IllegalStateException: Queue full
, but that feels too hacky.
抛出一个java.lang.IllegalStateException: Queue full
,但这感觉太hacky了。
Or you could use a reduction combined with an optional:
或者,您可以将减少与可选结合使用:
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();
The reduction essentially returns:
减少基本上返回:
- null if no user is found
- the user if only one is found
- throws an exception if more than one is found
- 如果未找到用户,则为 null
- 如果只找到一个用户
- 如果找到多个则抛出异常
The result is then wrapped in an optional.
然后将结果包装在一个可选项中。
But the simplest solution would probably be to just collect to a collection, check that its size is 1 and get the only element.
但最简单的解决方案可能是只收集到一个集合,检查它的大小是否为 1 并获取唯一的元素。
回答by Louis Wasserman
Guavaprovides MoreCollectors.onlyElement()
which does the right thing here. But if you have to do it yourself, you could roll your own Collector
for this:
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);
}
...or using your own Holder
type instead of AtomicReference
. You can reuse that Collector
as much as you like.
...或使用您自己的Holder
类型而不是AtomicReference
. 您可以随心所欲地重复使用它Collector
。
回答by pardeep131085
Have you tried this
你有没有试过这个
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.
Source: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
来源:https: //docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
回答by Stuart Marks
The other answers that involve writing a custom Collector
are probably more efficient (such as Louis Wasserman's, +1), but if you want brevity, I'd suggest the following:
涉及编写自定义的其他答案Collector
可能更有效(例如Louis Wasserman's,+1),但如果您想要简洁,我建议如下:
List<User> result = users.stream()
.filter(user -> user.getId() == 1)
.limit(2)
.collect(Collectors.toList());
Then verify the size of the result list.
然后验证结果列表的大小。
if (result.size() != 1) {
throw new IllegalStateException("Expected exactly one user but got " + result);
User user = result.get(0);
}
回答by Brian Goetz
The "escape hatch" operation that lets you do weird things that are not otherwise supported by streams is to ask for an Iterator
:
让你做一些流不支持的奇怪事情的“逃生舱口”操作是要求一个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 has a convenience method to take an Iterator
and get the only element, throwing if there are zero or multiple elements, which could replace the bottom n-1 lines here.
Guava 有一个方便的方法来Iterator
获取唯一的元素,如果有零个或多个元素则抛出,这可以替换这里的底部 n-1 行。
回答by prunge
An alternative is to use reduction:
(this example uses strings but could easily apply to any object type including User
)
另一种方法是使用reduction:(这个例子使用字符串,但可以很容易地应用于任何对象类型,包括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);};
}
So for the case with User
you would have:
因此,对于User
您的情况,将有:
User match = users.stream().filter((user) -> user.getId() < 0).reduce(thereCanBeOnlyOne()).get();
回答by glts
For the sake of completeness, here is the ‘one-liner' corresponding to @prunge's excellent answer:
为了完整起见,这是与@prunge 的优秀答案相对应的“单行”:
User user1 = users.stream()
.filter(user -> user.getId() == 1)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple elements: " + a + ", " + b);
})
.get();
This obtains the sole matching element from the stream, throwing
这从流中获取唯一匹配元素,抛出
NoSuchElementException
in case the stream is empty, orIllegalStateException
in case the stream contains more than one matching element.
NoSuchElementException
如果流为空,或IllegalStateException
如果流包含多个匹配元素。
A variation of this approach avoids throwing an exception early and instead represents the result as an Optional
containing either the sole element, or nothing (empty) if there are zero or multiple elements:
这种方法的一种变体避免了提前抛出异常,而是将结果表示为Optional
包含唯一元素,或者如果有零个或多个元素则不包含任何元素(空):
Optional<User> user1 = users.stream()
.filter(user -> user.getId() == 1)
.collect(Collectors.reducing((a, b) -> null));
回答by frhack
We can use RxJava(very powerful reactive extensionlibrary)
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();
The singleoperatorthrows an exception if no user or more then one user is found.
的单个操作者如果没有用户或多于一个用户被发现引发异常。
回答by John McClean
If you don't mind using a 3rd party library, SequenceM
from cyclops-streams(and LazyFutureStream
from simple-react) both a have single & singleOptional operators.
如果您不介意使用 3rd 方库,那么SequenceM
来自cyclops-streams(和LazyFutureStream
来自simple-react)都有 single 和 singleOptional 运算符。
singleOptional()
throws an exception if there are 0
or more than 1
elements in the Stream
, otherwise it returns the single value.
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()
returns Optional.empty()
if there are no values or more than one value in the Stream
.
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
Disclosure - I am the author of both libraries.
披露 - 我是两个图书馆的作者。