gpt4 book ai didi

Java 流 API : Looking for elegant way for filterAndMap

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:19:33 26 4
gpt4 key购买 nike

过滤和映射流的默认“最佳实践”是

Stream<T> source;
// ...
Predicate<T> predicate; // = ...
Function<T, U> mapper; // = ...
Stream<U> dst = source
.filter(predicate)
.map(mapper);

在许多软件项目中,您会遇到必须在多个流上应用相同的过滤器和映射操作的情况。例如,T 类对象的集合应该转换为 U 类对象的列表,其中 U 是 T 的子类,我们只需要 U 的实例。所以可以这样写:

Collection<T> source;
// ...
List<U> dst = source.stream()
.filter(U.class::isInstance)
.map(U.class::cast)
.collect(Collectors.toList());

为了概括这一点,我编写了一个名为 onlyInstancesOf 的帮助方法:

static <T, U> Function<T, Stream<U>> onlyInstancesOf(Class<U> clazz) {
return t -> clazz.isInstance(t)
? Stream.of(clazz.cast(t))
: Stream.empty();
}

此方法旨在与 flatMap 一起使用:

List<U> dst = source.stream()
.flatMap(onlyInstancesOf(U.class))
.collect(Collectors.toList());

我经常使用的另一个函数是 optionalPresent 来处理包含 Optionals 的流:

static <T> Function<Optional<T>, Stream<T>> optionalPresent() {
return t -> t.map(Stream::of).orElse(Stream.empty());
}

和用法:

Collection<Optional<T>> source;
// ...
List<T> dst = source.stream()
.flatMap(optionalPresent())
.collect(Collectors.toList());

这些解决方案第一眼看上去很优雅,但它们有一个很大的缺点:它们比先过滤再映射的“经典”解决方案慢 10 倍以上。

对于如何在不违反 DRY 原则的情况下处理这些经常使用的过滤和映射习语,您有什么建议?

最佳答案

你可以使用一个收集器(因为你总是以任何方式收集)来过滤某个类的实例:

static <T, U extends T> Collector<T, ?, List<U>> onlyInstancesOfCollector(Class<U> clazz) {
return Collector.of(
ArrayList::new,
(acc, e) -> {
if(clazz.isInstance(e)) {
acc.add(clazz.cast(e));
}
},
(a, b) -> {
a.addAll(b);
return a;
});
}

...

List<U> dst = source.stream()
.collect(onlyInstancesOfCollector(U.class));

哪个具有更好的性能特点:

Benchmark           Mode  Cnt  Score   Error  Units
Tests.collector avgt 10 0.171 ± 0.003 s/op
Tests.filterAndMap avgt 10 0.203 ± 0.005 s/op
Tests.flatmap avgt 10 0.375 ± 0.012 s/op

完整的 jmh 基准测试:

@BenchmarkMode({ Mode.AverageTime })
@Warmup(iterations = 25)
@Measurement(iterations = 10)
@State(Scope.Benchmark)
public class Tests {

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(Tests.class.getSimpleName())
.build();
new Runner(opt).run();
}

List<A> input;

@Setup
public void setup() {
Random r = new Random();
input = new ArrayList<>();
for(int i = 0; i < 10_000_000; i++) {
input.add(r.nextInt(2) == 0 ? new A() : new B());
}
}

@Fork(1)
@Benchmark
public List<B> filterAndMap() {
return input.stream()
.filter(B.class::isInstance)
.map(B.class::cast)
.collect(Collectors.toList());
}

@Fork(1)
@Benchmark
public List<B> flatmap() {
return input.stream()
.flatMap(onlyInstancesOf(B.class))
.collect(Collectors.toList());
}

@Fork(1)
@Benchmark
public List<B> collector() {
return input.stream()
.collect(onlyInstancesOfCollector(B.class));
}

static <T, U> Function<T, Stream<U>> onlyInstancesOf(Class<U> clazz) {
return t -> clazz.isInstance(t)
? Stream.of(clazz.cast(t))
: Stream.empty();
}

static <T, U extends T> Collector<T, ?, List<U>> onlyInstancesOfCollector(Class<U> clazz) {
return Collector.of(
ArrayList::new,
(acc, e) -> {
if(clazz.isInstance(e)) {
acc.add(clazz.cast(e));
}
},
(a, b) -> {
a.addAll(b);
return a;
});
}

}

class A {}
class B extends A {}

关于Java 流 API : Looking for elegant way for filterAndMap,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47906911/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com