简体   繁体   English

Java 8流中的转换类型

[英]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. 为了获得Java新流的一些经验,我一直在开发一个处理扑克牌的框架。 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 ): 这是我的代码的第一个版本,用于创建一个包含手中每个套装的卡数的MapSuitenum ):

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. 然后我重构,为“Suit Cards”和Jokers创建单独的Card子类。 So the getSuit() method was moved from the Card class to its subclass SuitCard , since Jokers don't have a suit. 所以getSuit()方法从Card类移到了它的子类SuitCard ,因为Jokers没有套装。 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 . 显然, collect线没有意识到它被传递的对象被保证是一个SuitCard

After puzzling over this for a good while, in desperation I tried inserting a map function call, and amazingly it worked! 在困惑了一段时间后,我绝望地尝试插入一个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() ) );

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? 为什么编译器需要它?

Remember that a filter operation will not change the compile-time type of the Stream 's elements. 请记住, filter操作不会更改Stream元素的编译时类型。 Yes, logically we see that everything that makes it past this point will be a SuitCard , all that the filter sees is a Predicate . 是的,从逻辑SuitCard ,我们看到所有超越这一点的东西都是一个SuitCardfilter看到的只是一个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: 如果要将其更改为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() ) );

I refer you to the Javadoc for the full details. 我将您介绍给Javadoc以获取完整的详细信息。

Well, map() allows transforming a Stream<Foo> into a Stream<Bar> using a function that takes a Foo as argument and returns a Bar . 好吧, map()允许使用一个以Foo为参数并返回Bar的函数将Stream<Foo>转换为Stream<Bar> And

card -> (SuitCard) card

is such a function: it takes a Card as argument and returns a SuitCard. 是这样一个功能:它将一个卡作为参数并返回一个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> . 编译器使之成为必要,因为filter()将Stream<Card>转换为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. 因此,您不能仅将接受SuitCard的函数应用于该流的元素,该元素可能包含任何类型的卡:编译器不关心过滤器的作用。 It only cares about what type it returns. 它只关心它返回的类型。

The type of contents is Card , so contents.stream() returns Stream<Card> . 内容的类型是Card ,因此contents.stream()返回Stream<Card> Filter does guarantee that each item in the resulting stream is a SuitCard , however, filter does not change the type of the stream. 过滤器确保结果流中的每个项目都是SuitCard ,但过滤器不会更改流的类型。 card -> (SuitCard)card is functionally equivalent to card -> card , but it's type is Function<Card,Suitcard> , so the .map() call returns a Stream<SuitCard> . card -> (SuitCard)card在功能上等同于card -> card ,但它的类型是Function<Card,Suitcard> ,因此.map()调用返回Stream<SuitCard>

Actually the problem is that you have a Stream<Card> type, even though after filtering you are pretty sure that the stream contains nothing but SuitCard objects. 实际上问题是你有一个Stream<Card>类型,即使在过滤后你很确定该流只包含SuitCard对象。 You know that, but compiler does not. 你知道,但编译器没有。 If you don't want to add an executable code into your stream, you can do instead an unchecked cast to Stream<SuitCard> : 如果您不想在流中添加可执行代码,则可以将未经检查的强制转换为Stream<SuitCard>

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

This way casting will not add any instructions to compiled bytecode. 这种方式不会向编译的字节码添加任何指令。 Unfortunately this looks quite ugly and produces a compiler warning. 不幸的是,这看起来非常难看并产生编译器警告。 In my StreamEx library I hid this ugliness inside library method select() , so using StreamEx you can write 在我的StreamEx库中,我在库方法select()隐藏了这个丑陋,所以使用StreamEx你可以编写

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

Or even shorter: 甚至更短:

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

If you don't like using third-party libraries, your solution involving additional map step looks ok. 如果您不喜欢使用第三方库,那么涉及其他map步骤的解决方案看起来不错。 Even though it adds some overhead, usually it's not very significant. 即使它增加了一些开销,通常它也不是很重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM