Java 8流中的转换类型

时间:2016-10-03 21:32:20

标签: java lambda casting java-8

为了获得Java新流的一些经验,我一直在开发一个处理扑克牌的框架。这是我的代码的第一个版本,用于创建Map,其中包含手中每件套装的卡片数量(Suitenum):

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

这很有效,我很高兴。然后我重构,为“Suit Cards”和Jokers创建单独的Card子类。因此,getSuit()方法已从Card类移至其子类SuitCard,因为Jokers没有诉讼。新代码:

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

请注意巧妙插入过滤器以确保正在考虑的卡实际上是西装卡而不是小丑。但它不起作用!显然,collect行没有意识到它被传递的对象被保证为SuitCard

在困惑了一段时间后,我绝望地尝试插入一个map函数调用,令人惊讶的是它有效!

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() ) );

我不知道将类型转换为可执行语句。为什么这样做?为什么编译器需要它?

4 个答案:

答案 0 :(得分:23)

请记住,filter操作不会更改Stream元素的编译时类型。是的,从逻辑上讲,我们发现超过此点的所有内容都是SuitCardfilter看到的所有内容都是Predicate。如果该谓词稍后更改,则可能导致其他编译时问题。

如果您想将其更改为Stream<SuitCard>,则需要添加一个为您执行投射的映射器:

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() ) );

我会向您推荐Javadoc以获取完整的详细信息。

答案 1 :(得分:13)

好吧,std::pair<int, int> lowestNHighestPrices() const { return {lowestPrice, highestPrice}; } 允许使用以map()为参数的函数将Stream<Foo>转换为Stream<Bar>并返回Foo。并且

Bar

就是这样一个函数:它将一个Card作为参数并返回一个SuitCard。

如果您愿意,可以这样写,也许这会让您更清楚:

card -> (SuitCard) card

编译器必须这样做,因为filter()会将new Function<Card, SuitCard>() { @Override public SuitCard apply(Card card) { SuitCard suitCard = (SuitCard) card; return suitCard; } } 转换为Stream<Card>。因此,您无法仅将接受SuitCard的函数应用于该流的元素,该元素可能包含任何类型的卡:编译器并不关心过滤器的作用。它只关心它返回的类型。

答案 2 :(得分:1)

内容类型为Card,因此contents.stream()会返回Stream<Card>。过滤器确保结果流中的每个项目都是SuitCard,但过滤器不会更改流的类型。 card -> (SuitCard)card在功能上等同于card -> card,但其类型为Function<Card,Suitcard>,因此.map()调用会返回Stream<SuitCard>

答案 3 :(得分:0)

实际上问题是你有一个Stream<Card>类型,即使在过滤后你很确定该流只包含SuitCard个对象。你知道,但编译器没有。如果您不想在流媒体中添加可执行代码,则可以执行未经检查的强制转换为Stream<SuitCard>

Map<Suit, Long> countBySuit = ((Stream<SuitCard>)contents.stream()
    .filter( card -> card instanceof SuitCard ))
    .collect( Collectors.groupingBy( SuitCard::getSuit, Collectors.counting() ) ); 

这种方式不会向编译的字节码添加任何指令。不幸的是,这看起来非常难看并产生编译器警告。在我的StreamEx库中,我将这种丑陋隐藏在库方法select()中,因此使用StreamEx可以编写

Map<Suit, Long> countBySuit = StreamEx.of(contents)
    .select( SuitCard.class )
    .collect( Collectors.groupingBy( SuitCard::getSuit, Collectors.counting() ) ); 

甚至更短:

Map<Suit, Long> countBySuit = StreamEx.of(contents)
    .select( SuitCard.class )
    .groupingBy( SuitCard::getSuit, Collectors.counting() ); 

如果您不想使用第三方库,则涉及其他map步骤的解决方案看起来不错。虽然它增加了一些开销,但通常它并不是很重要。