[英]Casting types in Java 8 streams
为了获得Java新流的一些经验,我一直在开发一个处理扑克牌的框架。 这是我的代码的第一个版本,用于创建一个包含手中每个套装的卡数的Map
( Suit
是enum
):
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() ) );
我不知道转换类型被认为是可执行语句。 为什么这样做? 为什么编译器需要它?
请记住, filter
操作不会更改Stream
元素的编译时类型。 是的,从逻辑SuitCard
,我们看到所有超越这一点的东西都是一个SuitCard
, filter
看到的只是一个Predicate
。 如果该谓词稍后更改,则可能导致其他编译时问题。
如果要将其更改为Stream<SuitCard>
,则需要添加一个为您执行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以获取完整的详细信息。
好吧, map()
允许使用一个以Foo
为参数并返回Bar
的函数将Stream<Foo>
转换为Stream<Bar>
。 和
card -> (SuitCard) card
是这样一个功能:它将一个卡作为参数并返回一个SuitCard。
如果你愿意,你可以这样写,也许这会让你更清楚:
new Function<Card, SuitCard>() {
@Override
public SuitCard apply(Card card) {
SuitCard suitCard = (SuitCard) card;
return suitCard;
}
}
编译器使之成为必要,因为filter()将Stream<Card>
转换为Stream<Card>
。 因此,您不能仅将接受SuitCard的函数应用于该流的元素,该元素可能包含任何类型的卡:编译器不关心过滤器的作用。 它只关心它返回的类型。
内容的类型是Card
,因此contents.stream()
返回Stream<Card>
。 过滤器确保结果流中的每个项目都是SuitCard
,但过滤器不会更改流的类型。 card -> (SuitCard)card
在功能上等同于card -> card
,但它的类型是Function<Card,Suitcard>
,因此.map()
调用返回Stream<SuitCard>
。
实际上问题是你有一个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
步骤的解决方案看起来不错。 即使它增加了一些开销,通常它也不是很重要。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.