我试图使用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(很好!)
但是,如果有多个匹配,我希望它抛出一个错误。有办法做到这一点吗?
创建一个自定义收集器
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);
更新
@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:队列已满,但感觉太粗糙了。
或者你可以使用减法和可选的结合:
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,并获得唯一的元素。
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。您可以尽可能多地重用收集器。
另一种选择是使用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();
为了完整起见,下面是@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
披露-我是这两个库的作者。
收藏家。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,代码是否会更可读。
使用收集器:
public static <T> Collector<T, ?, Optional<T>> singleElementCollector() {
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(singleElementCollector());
我们返回一个Optional,因为我们通常不能假设集合只包含一个元素。如果你已经知道这是什么情况,请致电:
User user = result.orElseThrow();
这就把处理错误的负担放在了调用者身上——这是应该的。
如果你不使用Guava或Kotlin,这里有一个基于@skiwi和@Neuron答案的解决方案。
users.stream().collect(single(user -> user.getId() == 1));
or
users.stream().collect(optional(user -> user.getId() == 1));
其中single和optional是返回相应收集器的静态导入函数。
我认为,如果将过滤逻辑移到收集器内部,看起来会更简洁。同样,如果您碰巧用.filter删除字符串,代码中也不会中断任何内容。
代码https://gist.github.com/overpas/ccc39b75f17a1c65682c071045c1a079的要点
public List<state> getAllActiveState() {
List<Master> master = masterRepository.getActiveExamMasters();
Master activeMaster = new Master();
try {
activeMaster = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {
throw new IllegalStateException();
}).get();
return stateRepository.getAllStateActiveId(activeMaster.getId());
} catch (IllegalStateException e) {
logger.info(":More than one status found TRUE in Master");
return null;
}
}
In this above code, As per the condition if its find more than one true in the list then it will through the exception.
When it through the error will showing custom message because it easy maintain the logs on server side.
From Nth number of element present in list just want only one element have true condition if in list there are more than one elements having true status at that moment it will through an exception.
after getting all the this we using get(); to taking that one element from list and stored it into another object.
If you want you added optional like Optional<activeMaster > = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {throw new IllegalStateException();}).get();