我有一组继承自共享类型的域对象(即GroupRecord扩展Record,RequestRecord extends Record).子类型具有特定属性(即GroupRecord :: getCumulativeTime,RequestRecord :: getResponseTime).
此外,由于解析日志文件,我有一个具有混合子类型的记录列表.
List<Record> records = parseLog(...);
为了计算日志记录的统计信息,我想仅在与特定子类型匹配的记录子集(即仅在GroupRecords上)应用数学函数.因此,我想要一个过滤的特定子类型的流.我知道我可以应用过滤器并映射到子类型
records.stream() .filter(GroupRecord.class::isInstance) .map(GroupRecord.class::cast) .collect(...
在流上多次应用此过滤器& cast(特别是对于不同的计算对于相同的子类型进行多次)不仅麻烦,而且产生大量的重复.
我目前的做法是使用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(...)
它有效,但我发现这种方法有点像这么简单的任务.因此,我想知道,使用流和功能以简明易懂的方式使这种行为可以重用吗最好的方法是什么?
解决方法
这取决于你发现什么“更简洁易读”.我自己会认为,你已经实施的方式很好,因为它是.
但是,确实有一种方法可以通过使用Stream.flatMap从您使用它的角度稍微短一点的方法:
static <E,T> Function<E,Stream<T>> onlyTypes(Class<T> cls) { return el -> cls.isInstance(el) ? Stream.of((T) el) : Stream.empty(); }
它将如何将每个原始流元素转换为一个元素的流,如果元素具有期望的类型,或者如果没有,则将其转换为空Stream.
使用的是:
records.stream() .flatMap(onlyTypes(GroupRecord.class)) .forEach(...);
这种方法有明显的权衡:
>您将丢失管道定义中的“过滤器”字.这可能更混乱,原来的,所以也许比只有类型更好的名字是需要的.
流对象相对重量级,创建这些对象可能会导致性能下降.但是你不应该相信我这里的话,并且在这个负载较大的情况下对两个变体进行分析
编辑:
由于问题要求稍微更一般地说重复使用过滤器和地图,我觉得这个答案也可以讨论一点点的抽象.所以,要重复使用过滤器和地图一般来说,您需要以下内容:
static <E,R> Function<E,Stream<R>> filterAndMap(Predicate<? super E> filter,Function<? super E,R> mapper) { return e -> filter.test(e) ? Stream.of(mapper.apply(e)) : Stream.empty(); }
而原来的只有类型实现现在变成:
static <E,Stream<R>> onlyTypes(Class<T> cls) { return filterAndMap(cls::isInstance,cls::cast); }
但是,现在还有一个折中:平坦的映射器函数现在将在上面的实现中捕获两个对象(谓词和映射器)而不是单个Class对象.这也可能是一个过度抽象的情况,但这取决于您需要该代码的位置和位置.