Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting types in Java 8 streams

Tags:

To gain some experience with Java's new streams, I've been developing a framework for handling playing cards. Here's the first version of my code for creating a Map containing the number of cards of each suit in a hand (Suit is an enum):

Map<Suit, Long> countBySuit = contents.stream() // contents is ArrayList<Card>  
        .collect( Collectors.groupingBy( Card::getSuit, Collectors.counting() ));

This worked great and I was happy. Then I refactored, creating separate Card subclasses for "Suit Cards" and Jokers. So the getSuit() method was moved from the Card class to its subclass SuitCard, since Jokers don't have a suit. New code:

Map<Suit, Long> countBySuit = contents.stream() // contents is ArrayList<Card>  
        .filter( card -> card instanceof SuitCard ) // reject Jokers
        .collect( Collectors.groupingBy( SuitCard::getSuit, Collectors.counting() ) );

Notice the clever insertion of a filter to make sure that the card being considered is in fact a Suit Card and not a Joker. But it doesn't work! Apparently the collect line doesn't realize that the object it's being passed is GUARANTEED to be a SuitCard.

After puzzling over this for a good while, in desperation I tried inserting a map function call, and amazingly it worked!

Map<Suit, Long> countBySuit = contents.stream() // contents is ArrayList<Card>  
        .filter( card -> card instanceof SuitCard ) // reject Jokers
        .map( card -> (SuitCard)card ) // worked to get rid of error message on next line
        .collect( Collectors.groupingBy( SuitCard::getSuit, Collectors.counting() ) );

I had no idea that casting a type was considered an executable statement. Why does this work? And why does the compiler make it necessary?

like image 588
Robert Lewis Avatar asked Oct 03 '16 21:10

Robert Lewis


People also ask

What are the types of streams in Java 8?

Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.

What are two types of streams in Java 8?

With Java 8, Collection interface has two methods to generate a Stream. stream() − Returns a sequential stream considering collection as its source. parallelStream() − Returns a parallel Stream considering collection as its source.

What is stream in JDK 8?

Introduced in Java 8, the Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result.


Video Answer


2 Answers

Remember that a filter operation will not change the compile-time type of the Stream's elements. Yes, logically we see that everything that makes it past this point will be a SuitCard, all that the filter sees is a Predicate. If that predicate changes later, then that could lead to other compile-time issues.

If you want to change it to a Stream<SuitCard>, you'd need to add a mapper that does a cast for you:

Map<Suit, Long> countBySuit = contents.stream() // Stream<Card>
    .filter( card -> card instanceof SuitCard ) // still Stream<Card>, as filter does not change the type
    .map( SuitCard.class::cast ) // now a Stream<SuitCard>
    .collect( Collectors.groupingBy( SuitCard::getSuit, Collectors.counting() ) );

I refer you to the Javadoc for the full details.

like image 100
Joe C Avatar answered Oct 30 '22 03:10

Joe C


Well, map() allows transforming a Stream<Foo> into a Stream<Bar> using a function that takes a Foo as argument and returns a Bar. And

card -> (SuitCard) card

is such a function: it takes a Card as argument and returns a SuitCard.

You could write it that way if you wanted to, maybe that makes it clearer to you:

new Function<Card, SuitCard>() {
    @Override
    public SuitCard apply(Card card) {
        SuitCard suitCard = (SuitCard) card;
        return suitCard;
    }
}

The compiler makes that necessary because filter() transforms a Stream<Card> into a Stream<Card>. So you can't apply a function only accepting SuitCard to the elements of that stream, which could contain any kind of Card: the compiler doesn't care about what your filter does. It only cares about what type it returns.

like image 39
JB Nizet Avatar answered Oct 30 '22 02:10

JB Nizet