Suppose we have a Stream of Animals.
We have different Subclasses of Animals, and we want to apply a filter on the stream to only have the Zebras of the Stream. We now still have a Stream of Animals, but only containing Zebras. To get a stream of Zebras we still need to cast.
Stream<Zebra> zebraStream = animalStream
.filter(Zebra.class::isInstance)
.map(Zebra.class::cast);
Java 14 introduced pattern matching for instanceof, so we can now use:
if (animal instanceof Zebra zebra) {
System.out.println(zebra.countStripes());
}
Is there a way to use pattern matching in stream pipes? Of course you could do something like this:
Stream<Zebra> zebraStream = animalStream.map(animal -> {
if (animal instanceof Zebra zebra) {
return zebra;
}
return null;
})
.filter(Objects::nonNull);
But IMHO this is really ugly.
CodePudding user response:
Pattern matching mapMulti
To coerce a Stream of supertype to a Stream of one of its subtypes, you can make use of the Pattern matching for instanceof in conjunction with Java 16 mapMulti()
, which expects a stream element and a Consumer
of the resulting type:
Stream<Animal> animalStream = Stream.of();
Stream<Zebra> zebraStream = animalStream
.mapMulti((animal, consumer) -> {
if (animal instanceof Zebra zebra) consumer.accept(zebra);
});
Pattern matching flatMap
To use Pattern matching for instanceof you can also employ a classic stream operation flatMap()
, which is like mapMulti()
is meant to perform one-to-many transformations.
The important distinction between the two is that mapMulti()
replace the initial stream element with zero or more elements via its Consumer
, meanwhile flatMap()
require a producing a new Stream to flatten the data. And in this case utilizing mapMulti()
would be more advantages because if the list is large generating singleton-streams for every element might be costful.
Stream<Zebra> zebraStream = animalStream
.flatMap(animal ->
animal instanceof Zebra zebra ? Stream.of(zebra) : null
);
Note that according to the documentation instead of an empty stream, we can also return null
(which is handy because Stream.empty()
doesn't return constant but spawns a new object):
If a mapped stream is
null
an empty stream is used, instead.
CodePudding user response:
I think you are almost done it! Just use filter
instead of map
:
Stream<Zebra> zebraStream = animalStream.stream()
.filter(animal -> animal instanceof Zebra)
.map(Zebra.class::cast);