Reputation: 18990
I'm having a Java 8 stream of numbers:
Stream<Number> numbers = ...;
I'd like to iterate the stream and invoke a specific consumer based on the type of each element. I.e. for Integer
elements I'd like to invoke a Consumer<Integer>
, for Long
a Consumer<Long>
etc.
There is the forEach()
method but this expects a Consumer<? super Number>
, requiring that implementation (usually a Lambda expression) to instanceof-switch on the exact type itself.
In terms of an API, I'm essentially looking for something like this:
numbers.forEach( callbacks -> {
callbacks.on( Integer.class, i -> { /* handle Integer */ } );
callbacks.on( Long.class, l -> { /* handle Long */ } )
} );
Is there any existing API which would allow me to register a specific consumer per stream element sub-type in a way similar to this?
Upvotes: 5
Views: 2062
Reputation: 29906
I do not know about such built-ins, so here is an option: Gather all the Consumer
s in a Map
keyed by the type it consumes, and look up the right consumer. I would recommend this solution if you work with a lot of types, and those types can change dynamically. The most efficient solution (by cpu consumption) would probably be to use a switch
on item.getType()
.
public class Casts {
public static void main(String[] args) {
Stream<Number> stream = Arrays.stream(new Number[] { 3, 4L });
Map<Class<?>, Consumer<? super Number>> consumers = new HashMap<>();
putCastConsumer(consumers, Long.class,
i -> System.out.println("Mapped long " + i));
putCastConsumer(consumers, Integer.class,
i -> System.out.println("Mapped int " + i));
consumeByType(stream, consumers);
}
public static <U, T extends U> void putCastConsumer(
final Map<Class<?>, Consumer<? super U>> map,
final Class<T> clazz,
final Consumer<T> consumer) {
map.put(clazz, value -> consumer.accept(clazz.cast(value)));
}
public static <T> void consumeByType(
final Stream<T> stream,
final Map<Class<?>, Consumer<? super T>> consumers) {
stream.forEach(item -> consumers.get(item.getClass()).accept(item));
}
}
Upvotes: 2
Reputation: 28133
Are you sure you don't want to just run the stream twice? It will be more readable.
But if you want, you can define a type-checking consumer like this:
public static<T> Consumer<Object> acceptType(Class<T> clazz, Consumer<? super T> cons) {
return t -> {
if (clazz.isInstance(t)) {
cons.accept(clazz.cast(t));
}
};
}
You can then combine multiple consumers using andThen
:
Consumer<Object> combined = acceptType(Integer.class, i -> ...)
.andThen(acceptType(Long.class, lng -> ...))
If you want to hide andThen
, you can define
static<T> Consumer<T> doAll(Consumer<T>... consumers) {
return Arrays.stream(consumers)
.reduce(Consumer::andThen)
.orElse(t -> {});
}
Then it becomes
nums.forEach(doAll(
acceptType(Integer.class, i -> ...),
acceptType(Long.class, lng -> ..),
...
));
Upvotes: 7