Casting types in Java 8 streams

Robert Lewis :

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?

Joe C :

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.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=449072&siteId=1