I have a set of domain objects that inherit from a shared type (i.e. GroupRecord extends Record
, RequestRecord extends Record
). The subtypes have speci
WHAT you actually need is a Collector to collecting all elements in the stream that is instance of special type. It can solving your problem easily and avoiding filtering the stream twice:
List result = records.stream().collect(
instanceOf(GroupRecord.class, Collectors.toList())
);
SomeStatsResult stats1 = result.stream().collect(...);
SomeStatsResult stats2 = result.stream().collect(...);
AND you can do something as further like as Stream#map by using Collectors#mapping, for example:
List result = Stream.of(1, 2L, 3, 4.)
.collect(instanceOf(Integer.class, mapping(it -> it * 2, Collectors.toList())));
| |
| [2,6]
[1,3]
WHERE you only want to consuming the Stream
once, you can easily composing the last Collector
as below:
SomeStatsResult stats = records.stream().collect(
instanceOf(GroupRecord.class, ...)
);
static Collector instanceOf(Class type
, Collector downstream) {
return new Collector() {
@Override
public Supplier supplier() {
return downstream.supplier();
}
@Override
public BiConsumer accumulator() {
BiConsumer target = downstream.accumulator();
return (result, it) -> {
if (type.isInstance(it)) {
target.accept(result, type.cast(it));
}
};
}
@Override
public BinaryOperator combiner() {
return downstream.combiner();
}
@Override
public Function finisher() {
return downstream.finisher();
}
@Override
public Set characteristics() {
return downstream.characteristics();
}
};
}
Did you remember Composition over Inheritance Principle? Did you remember assertThat(foo).isEqualTo(bar) and assertThat(foo, is(bar)) in unit-test?
Composition is much more flexible, it can reuses a piece of code and composeing components together on runtime, that is why I prefer hamcrest
rather than fest-assert
since it can composing all possible Matcher
s together. and that is why functional programming is most popular since it can reuses any smaller piece of function code than class level reusing. and you can see jdk has introduced Collectors#filtering in jdk-9 that will make the execution routes shorter without losing its expressiveness.
AND you can refactoring the code above according to Separation of Concerns as further, then filtering
can be reused like as jdk-9 Collectors#filtering:
static Collector instanceOf(Class type
, Collector downstream) {
return filtering(type::isInstance, Collectors.mapping(type::cast, downstream));
}
static
Collector filtering(Predicate super T> predicate
, Collector downstream) {
return new Collector() {
@Override
public Supplier supplier() {
return downstream.supplier();
}
@Override
public BiConsumer accumulator() {
BiConsumer target = downstream.accumulator();
return (result, it) -> {
if (predicate.test(it)) {
target.accept(result, it);
}
};
}
@Override
public BinaryOperator combiner() {
return downstream.combiner();
}
@Override
public Function finisher() {
return downstream.finisher();
}
@Override
public Set characteristics() {
return downstream.characteristics();
}
};
}