我正在做一些Java 8流的“代数”,也就是说,我正在尝试编写一个简单的操作Op,它将两个流作为输入并产生另一个流。
所以我有这个简单的代码,其目的是在一系列数字中打印secund最高值:
import java.util.Arrays;
import java.util.stream.IntStream;
public class SecundHighestValue {
public static void main(String[] args) {
//setting the input parameters
int [] numbers = {1, 2, 3, 4, 3, 4, 2, 1};
IntStream S1 = Arrays.stream(numbers);
IntStream S2 = Arrays.stream(new int[] {Arrays.stream(numbers).max().getAsInt()} );
// setting the operation
IntStream S3 = S1.filter(x-> x != S2.toArray()[0]); // doesn't work
/*** does work ***
int maxNumber = S2.toArray()[0];
IntStream S3 = S1.filter(x-> x != maxNumber);
*/
// accessing the operation's result stream S3
int secundMaxNumber = S3.max().getAsInt();
System.out.println("the secund highest value in the serie " +
Arrays.toString(numbers) + " is " + secundMaxNumber);
}
}
除非我以这种方式拆分单行操作,否则此程序将无法运行:
int maxNumber = S2.toArray()[0];
IntStream S3 = S1.filter(x-> x != maxNumber);
将操作保持在一行会引发此异常:
线程“main”中的异常java.lang.IllegalStateException:stream已经被操作或关闭 ... 的
我知道它与filter()方法固有的懒惰有关。 API解释说:
流操作分为中间(流生成)操作和终端(生成价值或副作用)操作。中间操作总是很懒惰。
实际上,堆栈跟踪显示在我尝试在下一行中访问其结果之前,操作不会执行。
这种行为在java8中是否有缺陷?这是一个错误吗?最重要的是,如何将操作保持在一行并使其工作?
答案 0 :(得分:6)
如果通过源进行流式传输是可能的并且不是很昂贵,例如数组,您可以只流式传输两次,例如azro’s answer:
int maxNumber = Arrays.stream(numbers).max().getAsInt();
int secondMaxNumber = Arrays.stream(numbers).filter(x-> x != maxNumber).max().getAsInt();
如果无法进行两次流式传输或昂贵,则需要一个自定义收集器来有效地获得第二大值,即只保留必要的两个值。 E.g。
final class SecondMax {
long max=Long.MIN_VALUE, semi=max;
void add(int next) {
if(next>semi) {
if(next>max) {
semi=max;
max=next;
}
else if(next<max) {
semi=next;
}
}
}
void merge(SecondMax other) {
if(other.max>Long.MIN_VALUE) {
add((int)other.max);
if(other.semi>Long.MIN_VALUE) add((int)other.semi);
}
}
OptionalInt get() {
return semi>Long.MIN_VALUE? OptionalInt.of((int)semi): OptionalInt.empty();
}
}
使用此帮助程序,您可以在单个流操作中获取值:
OptionalInt secondMax = Arrays.stream(array)
.collect(SecondMax::new, SecondMax::add, SecondMax::merge).get();
答案 1 :(得分:2)
你有四行:
IntStream S1 = Arrays.stream(numbers);
IntStream S2 = Arrays.stream(new int[] {Arrays.stream(numbers).max().getAsInt()} );
int maxNumber = S2.toArray()[0];
IntStream S3 = S1.filter(x-> x != maxNumber);
int secundMaxNumber = S3.max().getAsInt();
同样在2:
int maxNumber = Arrays.stream(numbers).max().getAsInt();
int secundMaxNumber = Arrays.stream(numbers).filter(x-> x != maxNumber).max().getAsInt();
很难重新使用流,以便更好地单向执行,更好地计算变量中的最大值并重新使用,以便每次都不计算
答案 2 :(得分:2)
这不起作用的原因:
IntStream S3 = S1.filter(x-> x != S2.toArray()[0]);
是因为S2只能执行一次。和过滤器重新计算S3中的每个条目。
将其视为for循环,将s2视为只能准备一次的值。 您可以将流与System.in进行比较 - 一旦您无法重新读取它,就会读取该值。你必须得到一个新的。
更多信息: 该操作不是懒惰的,因为你有这行代码使它成为终端:
secundMaxNumber = S3.max().getAsInt();
附注:要获得Xth maxNumber,您也可以这样做:您不需要多次使用该流。
S1.sorted().limit(x).skip(x-1).findFirst().getAsInt();
参考文献:
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#limit-long-
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#skip-long-
答案 3 :(得分:1)
由于输入是一个int数组,@ azro提供的解决方案对我来说已经足够了。只是第二个@Holger:不必定义新类:
final Supplier<int[]> supplier = () -> new int[] { Integer.MIN_VALUE, Integer.MIN_VALUE };
final ObjIntConsumer<int[]> accumulator = (a, i) -> {
if (i > a[0]) {
a[1] = a[0];
a[0] = i;
} else if (i != a[0] && i > a[1]) {
a[1] = i;
}
};
int secondMax = Arrays.stream(nums).collect(supplier, accumulator, (a, b) -> {})[1];
或使用第三方库中提供的API:AbacusUtil
int secondMax = IntStream.of(nums).distinct().kthLargest(2).get();