
在处理数据流(stream)时,我们经常会遇到需要根据多个优先级条件进行查找的需求。例如,给定一个字符串stream,我们希望:
以下是具体的示例及其预期结果:
Stream<String> stream0 = Stream.of("a", "b", "c", "d");
Stream<String> stream1 = Stream.of("b", "c", "d", "a");
Stream<String> stream2 = Stream.of("b", "c", "d", "e");
Stream<String> stream3 = Stream.of("d", "e", "f", "g");
// 预期结果:
// findBestValue(stream0); // 应该返回 "a"
// findBestValue(stream1); // 应该返回 "a"
// findBestValue(stream2); // 应该返回 "b"
// findBestValue(stream3); // 应该返回 null许多开发者在初次尝试解决此类问题时,可能会直观地使用链式filter().findFirst().orElse()结构,如下所示:
private static String findBestValue(Stream<String> stream) {
return stream.filter(str -> str.equals("a"))
.findFirst()
.orElse(stream.filter(str -> str.equals("b")) // 这里会出错
.findFirst()
.orElse(stream.filter(str -> str.equals("c"))
.findFirst()
.orElse(null))
);
}然而,上述代码在执行时会抛出java.lang.IllegalStateException: stream has already been operated upon or closed异常。这是因为Java Stream的一个核心特性是它只能被消费一次。一旦Stream上的终端操作(如findFirst()、collect()、forEach()等)被调用,该Stream就被认为是已操作或已关闭,不能再进行任何操作。
Stream并非数据容器,而是一种数据源的迭代器。正如Java API文档所述:
立即学习“Java免费学习笔记(深入)”;
无存储。Stream不是存储元素的数据结构;相反,它通过计算操作管道,从数据结构、数组、生成器函数或I/O通道等源传递元素。
因此,在上述错误示例中,当第一个stream.filter(str -> str.equals("a")).findFirst()执行完毕后,stream实例就已经被消费了。后续orElse()中尝试再次对同一个stream实例进行filter操作时,就会触发IllegalStateException。
为了解决Stream只能消费一次的问题,同时又要进行多次条件判断,最直接且推荐的方法是将Stream中的数据首先收集到一个可复用的数据结构中,例如Map或List。对于本场景,由于我们需要根据键值进行查找,Map是一个非常合适的选择。
我们可以将Stream中的所有元素收集到一个LinkedHashMap中。LinkedHashMap不仅存储了元素,还保留了元素的插入顺序,这在某些需要维护原始顺序的场景中非常有用。
首先,我们提供一个接受特定优先级键值的方法。
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamPrioritySearch {
/**
* 从Stream中查找第一个匹配指定优先级键值的元素。
*
* @param stream 待处理的Stream。
* @param key1 最高优先级键。
* @param key2 次高优先级键。
* @param key3 第三优先级键。
* @param <T> Stream中元素的类型。
* @return 匹配到的第一个元素,如果都没有匹配则返回null。
*/
private static <T> T findBestValue(Stream<T> stream, T key1, T key2, T key3) {
// 1. 将Stream中的所有元素收集到LinkedHashMap中。
// Function.identity() 作为键和值,表示元素本身。
// (l, r) -> l 用于处理重复键,保留第一个遇到的值。
// LinkedHashMap::new 确保保持插入顺序。
Map<T, T> map = stream.collect(Collectors.toMap(
Function.identity(), // 元素作为键
Function.identity(), // 元素作为值
(l, r) -> l, // 合并函数:如果键重复,保留第一个遇到的值
LinkedHashMap::new // 使用LinkedHashMap保持插入顺序
));
// 2. 遍历优先级键,从map中查找对应的值。
// 创建一个包含所有优先级键的Stream。
// map(map::get) 将键映射到map中的值(如果存在)。
// filter(Objects::nonNull) 过滤掉map中不存在的键(即返回null的情况)。
// findFirst() 找到第一个非null的值。
// orElse(null) 如果所有优先级键在map中都不存在,则返回null。
return Stream.of(map.get(key1), map.get(key2), map.get(key3))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
// 主方法用于测试
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("a", "b", "c", "d");
Stream<String> stream2 = Stream.of("b", "c", "d", "e");
Stream<String> stream3 = Stream.of("d", "e", "f", "g");
System.out.println("Stream1 (a,b,c): " + findBestValue(stream1, "a", "b", "c")); // 预期: a
System.out.println("Stream2 (a,b,c): " + findBestValue(stream2, "a", "b", "c")); // 预期: b
System.out.println("Stream3 (a,b,c): " + findBestValue(stream3, "a", "b", "c")); // 预期: null
// 注意:Stream一旦被消费就不能再用,所以每次测试都需要新的Stream实例
System.out.println("Stream0 (a,b,c): " + findBestValue(Stream.of("a", "b", "c", "d"), "a", "b", "c")); // 预期: a
System.out.println("Stream1 (b,a,c): " + findBestValue(Stream.of("b", "c", "d", "a"), "b", "a", "c")); // 预期: b (因为b优先级更高)
}
}为了使findBestValue方法更加通用和灵活,我们可以让它接受一个可变参数(varargs)来表示任意数量的优先级键。
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamPrioritySearchOptimized {
/**
* 从Stream中查找第一个匹配指定优先级键值的元素。
*
* @param stream 待处理的Stream。
* @param keys 一个或多个优先级键,按顺序排列。
* @param <T> Stream中元素的类型。
* @return 匹配到的第一个元素,如果都没有匹配则返回null。
*/
private static <T> T findBestValue(Stream<T> stream, T... keys) {
// 1. 将Stream中的所有元素收集到LinkedHashMap中。
Map<T, T> map = stream.collect(Collectors.toMap(
Function.identity(),
Function.identity(),
(l, r) -> l,
LinkedHashMap::new
));
// 2. 遍历优先级键,从map中查找对应的值。
// Arrays.stream(keys) 创建一个包含所有优先级键的Stream。
// map(map::get) 将键映射到map中的值。
// filter(Objects::nonNull) 过滤掉map中不存在的键。
// findFirst() 找到第一个非null的值。
// orElse(null) 如果所有优先级键在map中都不存在,则返回null。
return Arrays.stream(keys)
.map(map::get)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
// 主方法用于测试
public static void main(String[] args) {
// 注意:Stream一旦被消费就不能再用,所以每次测试都需要新的Stream实例
System.out.println("Stream0 (a,b,c): " + findBestValue(Stream.of("a", "b", "c", "d"), "a", "b", "c")); // 预期: a
System.out.println("Stream1 (a,b,c): " + findBestValue(Stream.of("b", "c", "d", "a"), "a", "b", "c")); // 预期: a
System.out.println("Stream2 (a,b,c): " + findBestValue(Stream.of("b", "c", "d", "e"), "a", "b", "c")); // 预期: b
System.out.println("Stream3 (a,b,c): " + findBestValue(Stream.of("d", "e", "f", "g"), "a", "b", "c")); // 预期: null
System.out.println("Stream4 (e,f,g): " + findBestValue(Stream.of("d", "e", "f", "g"), "e", "f", "g")); // 预期: e
System.out.println("Stream5 (z,y,x): " + findBestValue(Stream.of("d", "e", "f", "g"), "z", "y", "x")); // 预期: null
}
}运行上述main方法,将得到以下输出:
Stream0 (a,b,c): a Stream1 (a,b,c): a Stream2 (a,b,c): b Stream3 (a,b,c): null Stream4 (e,f,g): e Stream5 (z,y,x): null
这完美符合了我们的预期。
return Arrays.stream(keys)
.map(map::get)
.filter(Objects::nonNull)
.findFirst(); // 返回 Optional<T>调用方可以使用optionalValue.orElse(defaultValue)、optionalValue.orElseThrow()或optionalValue.ifPresent()等方法。
通过将Stream数据一次性收集到可复用的集合中,我们能够优雅且高效地解决Java Stream中多条件优先级查找的问题,同时避免了IllegalStateException。这种模式在处理复杂的数据过滤和查找逻辑时非常有用。
以上就是Java Stream多条件优先级查找:避免“Stream已操作或关闭”异常的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号