Robert Lewis Robert Lewis - 21 days ago 8x
Java Question

Casting types in Java 8 streams

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

containing the number of cards of each suit in a hand (
is an

Map<Suit, Long> countBySuit = // 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
method was moved from the
class to its subclass
, since Jokers don't have a suit. New code:

Map<Suit, Long> countBySuit = // 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
line doesn't realize that the object it's being passed is GUARANTEED to be a

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

Map<Suit, Long> countBySuit = // 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?


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>() {
    public void 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.