Home > database >  Applying Pattern matching for Instanceof in the Stream
Applying Pattern matching for Instanceof in the Stream

Time:11-13

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);
  • Related