我试图使用Java 8流在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 match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
将抛出一个NoSuchElementException(很好!)
但是,如果有多个匹配,我希望它抛出一个错误。有办法做到这一点吗?
另一种选择是使用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);};
}
所以对于User的情况,你会有:
User match = users.stream().filter((user) -> user.getId() < 0).reduce(thereCanBeOnlyOne()).get();
创建一个自定义收集器
public static <T> Collector<T, ?, T> toSingleton() {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> {
if (list.size() != 1) {
throw new IllegalStateException();
}
return list.get(0);
}
);
}
我们使用收集工具。然后构造我们想要的收集器
使用collector. tolist()收集器在列表中收集对象。
在最后应用一个额外的结束符,返回单个元素—或者抛出一个IllegalStateException if列表。Size != 1。
用作:
User resultUser = users.stream()
.filter(user -> user.getId() > 0)
.collect(toSingleton());
然后,您可以随心所欲地定制这个Collector,例如在构造函数中将异常作为参数,将其调整为允许两个值,甚至更多。
另一种可能不那么优雅的解决方案是:
您可以使用包含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));
如果你不介意使用第三方库,来自cyclops-streams的SequenceM(和来自simple-react的LazyFutureStream)都有single和singleOptional操作符。
如果流中有0个或多个元素,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();
如果流中没有值或有多个值,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
披露-我是这两个库的作者。