为避免两次运行,您可以提供自己的Collector 来收集流。
让我们使用
示例数据类
static class MyCustomObject {
private int intField;
MyCustomObject(int field) {
intField = field;
}
public int getIntField() {
return intField;
}
@Override
public String toString() {
return Integer.toString(intField);
}
}
创建自己的Collector 是使用工厂方法之一,Collector#of。我们将使用the more complex one。
这就是它的样子:
Collector<MyCustomObject, Intermediate, List<MyCustomObject>> collector
MyCustomObject 是您正在收集的对象,Intermediate 是一个将存储当前最大值和具有该最大值的 MyCustomObjects 列表的类,以及 List<MyCustomObject>> 具有该最大值的对象的所需最终结果最大。
中级
这是中间类:
// simple enough
class Intermediate {
Integer val = null;
List<MyCustomObject> objects = new ArrayList<>();
}
这将保留最大和相应的对象。
它将提供
Supplier<Intermediate> supplier = () -> new Intermediate();
(或短的 Intermediate::new)。
累加器
accumulator 需要将新的MyCustomObject 累积到现有的Intermediate 中。这就是计算最大值的逻辑所在。
BiConsumer<Intermediate, MyCustomObject> accumulator = (i, c) -> {
System.out.printf("accumulating %d into %d%n", c.intField, i.value);
if (i.value != null) {
if (c.intField > i.value.intValue()) {
// new max found
System.out.println("new max " + c.intField);
i.value = c.intField;
i.objects.clear();
} else if (c.intField < i.value) {
// smaller than previous max: ignore
return;
}
} else {
i.value = c.intField;
}
i.objects.add(c);
};
组合器
combiner 用于组合两个 Intermediate 值。这用于并行流。如果您执行下面的简单测试运行,您将不会触发它。
BinaryOperator<Intermediate> combiner = (i1, i2) -> {
System.out.printf("combining %d and %d%n", i1.value, i2.value);
Intermediate result = new Intermediate();
result.value = Math.max(i1.value, i2.value);
if (i1.value.intValue() == result.value.intValue()) {
result.objects.addAll(i1.objects);
}
if (i2.value.intValue() == result.value.intValue()) {
result.objects.addAll(i2.objects);
}
return result;
};
整理者
最后,我们需要使用finisher从最终的Intermediate中提取出我们真正想要的List<MyCustomObject>
Function<Intermediate, List<MyCustomObject>> finisher = i -> i.objects;
这一切都是为了Collector
Collector<MyCustomObject, Intermediate, List<MyCustomObject>> collector =
Collector.of(supplier, accumulator, combiner, finisher);
对于一个简单的测试运行
List<MyCustomObject> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 3; j++) {
list.add(new MyCustomObject(i));
}
}
Collections.shuffle(list);
System.out.println(list.stream().collect(collector));
输出
[9, 9, 9]
我们只迭代一次,所以它应该是 O(n) 作为两次运行的解决方案;我对此并不完全确定,因为所有添加到列表都发生在中间步骤中。
See it tied together
对于实际的Comparator 版本,您还必须调整Intermediate 对象;那么最好在Intermediate 中使用MyCustomObject 来进行比较。
Here is a version for this,包括将累加器重构为Intermediate 类。
最后归结为这个工厂方法:
public static <T> Collector<T, ?, List<T>> max(Comparator<T> compare) {
class Intermediate {
T value = null;
List<T> objects = new ArrayList<>();
void add(T c) {
if (objects.isEmpty()) {
value = c;
} else {
int compareResult = compare.compare(c, objects.get(0));
if (compareResult > 0) {
// new max found
System.out.println("new max " + c + ", dropping " + objects.size() + " objects");
value = c;
objects.clear();
} else if (compareResult < 0) {
return;
}
}
objects.add(c);
}
}
BinaryOperator<Intermediate> combiner = (i1, i2) -> {
Optional<T> max = Stream.of(i1, i2).filter(Objects::nonNull).filter(i -> !i.objects.isEmpty())
.map(i -> i.objects.get(0)).max(compare);
Intermediate r = max.map(m -> {
Intermediate result = new Intermediate();
result.value = max.get();
if (i1 != null && i1.value != null && compare.compare(i1.value, m) == 0) {
result.objects.addAll(i1.objects);
}
if (i2 != null && i2.value != null && compare.compare(i2.value, m) == 0) {
result.objects.addAll(i2.objects);
}
return result;
}).orElse(null);
System.out.printf("combining %s and %s - result %s%n", i1, i2, r);
return r;
};
return Collector.of(Intermediate::new, Intermediate::add, combiner, i -> i.objects);
}