Java 8 流中的类型转换

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39840749/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 21:29:33  来源:igfitidea点击:

Casting types in Java 8 streams

javalambdacastingjava-8

提问by Robert Lewis

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 Mapcontaining the number of cards of each suit in a hand (Suitis an enum):

为了获得一些 Java 新流的经验,我一直在开发一个处理扑克牌的框架。这是我的代码的第一个版本,用于创建Map包含手牌中每种花色的牌数(Suitis 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 Cardclass to its subclass SuitCard, since Jokers don't have a suit. New code:

这很有效,我很高兴。然后我重构,为“西装卡”和小丑创建单独的卡子类。所以这个getSuit()方法从Card类移动到它的子类SuitCard,因为小丑没有西装。新代码:

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 collectline 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 mapfunction 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?

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

采纳答案by Joe C

Remember that a filteroperation 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 filtersees is a Predicate. If that predicate changes later, then that could lead to other compile-time issues.

请记住,filter操作不会更改Stream元素的编译时类型。是的,从逻辑上讲,我们看到通过这一点的一切都将是 a SuitCard,所有filter看到的都是 a Predicate。如果该谓词稍后更改,则可能会导致其他编译时问题。

If you want to change it to a Stream<SuitCard>, you'd need to add a mapper that does a cast for you:

如果要将其更改为 a 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 Javadocfor the full details.

我建议您参阅Javadoc以获取完整的详细信息。

回答by JB Nizet

Well, map()allows transforming a Stream<Foo>into a Stream<Bar>using a function that takes a Fooas argument and returns a Bar. And

那么,map()允许转化Stream<Foo>Stream<Bar>用一个函数,一个Foo作为参数和返回Bar。和

card -> (SuitCard) card

is such a function: it takes a Card as argument and returns a SuitCard.

是这样一个函数:它接受一个 Card 作为参数并返回一个 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.

编译器需要这样做,因为 filter() 将 aStream<Card>转换为 a Stream<Card>。因此,您不能将仅接受 SuitCard 的函数应用于该流的元素,该流可以包含任何类型的 Card:编译器不关心您的过滤器的作用。它只关心它返回什么类型。

回答by Andrew Rueckert

The type of contents is Card, so contents.stream()returns 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. card -> (SuitCard)cardis functionally equivalent to card -> card, but it's type is Function<Card,Suitcard>, so the .map()call returns a Stream<SuitCard>.

内容的类型是Card,所以contents.stream()返回Stream<Card>。Filter 确实保证结果流中的每个项目都是 a SuitCard,但是, filter 不会更改流的类型。card -> (SuitCard)card在功能上等同于card -> card,但它的类型是Function<Card,Suitcard>,因此.map()调用返回一个Stream<SuitCard>

回答by Tagir Valeev

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 SuitCardobjects. 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<Card>类型,即使在过滤之后你很确定流只包含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 StreamExlibrary 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 mapstep looks ok. Even though it adds some overhead, usually it's not very significant.

如果您不喜欢使用第三方库,则涉及额外map步骤的解决方案看起来不错。即使它增加了一些开销,通常也不是很重要。