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?
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With