我有一组从共享类型继承的域对象(即GroupRecord extends Record
,RequestRecord extends Record
)。子类型具有特定属性(即GroupRecord::getCumulativeTime
,RequestRecord::getResponseTime
)。
此外,由于解析了日志文件,我有一个混合子类型的记录列表。
List<Record> records = parseLog(...);
为了计算日志记录的统计数据,我想仅在与特定子类型匹配的记录子集上应用数学函数,即仅在GroupRecord
上。因此,我希望有一个特定子类型的过滤流。我知道我可以使用
filter
和map
应用于子类型
records.stream()
.filter(GroupRecord.class::isInstance)
.map(GroupRecord.class::cast)
.collect(...
在流上多次应用此过滤器和放置(特别是当针对不同的计算多次执行相同的子类型时)不仅麻烦,而且会产生大量重复。
我目前的做法是使用TypeFilter
class TypeFilter<T>{
private final Class<T> type;
public TypeFilter(final Class<T> type) {
this.type = type;
}
public Stream<T> filter(Stream<?> inStream) {
return inStream.filter(type::isInstance).map(type::cast);
}
}
要应用于流:
TypeFilter<GroupRecord> groupFilter = new TypeFilter(GroupRecord.class);
SomeStatsResult stats1 = groupFilter.filter(records.stream())
.collect(...)
SomeStatsResult stats2 = groupFilter.filter(records.stream())
.collect(...)
它有效,但我发现这种方法对于这么简单的任务来说有点多了。因此我想知道,使用流和函数以简洁和可读的方式使这种行为可重用是否有更好或更好的方法是什么?
答案 0 :(得分:9)
您不需要整个类来封装一段代码。用于此目的的最小代码单元是一种方法:
public static <T> Stream<T> filter(Collection<?> source, Class<T> type) {
return source.stream().filter(type::isInstance).map(type::cast);
}
此方法可用作
SomeStatsResult stats1 = filter(records, GroupRecord.class)
.collect(...);
SomeStatsResult stats2 = filter(records, GroupRecord.class)
.collect(...);
如果过滤操作并不总是链中的第一步,则可能会重载方法:
public static <T> Stream<T> filter(Collection<?> source, Class<T> type) {
return filter(source.stream(), type);
}
public static <T> Stream<T> filter(Stream<?> stream, Class<T> type) {
return stream.filter(type::isInstance).map(type::cast);
}
但是,如果您必须多次为同一类型重复此操作,则执行
可能会有所帮助List<GroupRecord> groupRecords = filter(records, GroupRecord.class)
.collect(Collectors.toList());
SomeStatsResult stats1 = groupRecords.stream().collect(...);
SomeStatsResult stats2 = groupRecords.stream().collect(...);
不仅消除了源代码中的代码重复,而且只执行了一次运行时类型检查。所需额外堆空间的影响取决于实际用例。
答案 1 :(得分:3)
WHAT 是Collector来收集流中特殊类型实例的所有元素。它可以轻松解决您的问题,并避免过滤流两次:
List<GroupRecord> result = records.stream().collect(
instanceOf(GroupRecord.class, Collectors.toList())
);
SomeStatsResult stats1 = result.stream().collect(...);
SomeStatsResult stats2 = result.stream().collect(...);
AND 您可以使用Stream#map像Collectors#mapping那样做更多的事情,例如:
List<Integer> result = Stream.of(1, 2L, 3, 4.)
.collect(instanceOf(Integer.class, mapping(it -> it * 2, Collectors.toList())));
| |
| [2,6]
[1,3]
WHERE 您只想使用Stream
一次,您可以轻松编写上一个Collector
,如下所示:
SomeStatsResult stats = records.stream().collect(
instanceOf(GroupRecord.class, ...)
);
static <T, U extends T, A, R> Collector<T, ?, R> instanceOf(Class<U> type
, Collector<U, A, R> downstream) {
return new Collector<T, A, R>() {
@Override
public Supplier<A> supplier() {
return downstream.supplier();
}
@Override
public BiConsumer<A, T> accumulator() {
BiConsumer<A, U> target = downstream.accumulator();
return (result, it) -> {
if (type.isInstance(it)) {
target.accept(result, type.cast(it));
}
};
}
@Override
public BinaryOperator<A> combiner() {
return downstream.combiner();
}
@Override
public Function<A, R> finisher() {
return downstream.finisher();
}
@Override
public Set<Characteristics> characteristics() {
return downstream.characteristics();
}
};
}
你还记得Composition over Inheritance Principle吗?你还记得单元测试中的assertThat(foo).isEqualTo(bar)和assertThat(foo, is(bar))吗?
组合更强大灵活,它可以在运行时重用一段代码和撰写组件,这就是我喜欢的原因{ {1}}而不是hamcrest
,因为它可以将所有可能的fest-assert
组合在一起。这就是函数式编程最受欢迎的原因,因为它可以重用任何较小的函数代码而不是类级重用。你可以看到jdk在jdk-9中引入了Collectors#filtering,这将使执行路线缩短而不会失去其表现力。
AND 您可以根据Separation of Concerns进一步重构上述代码,然后可以像jdk-9 Collectors#filtering一样重复使用Matcher
:
filtering