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
Casting types in Java 8 streams
提问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 Map
containing the number of cards of each suit in a hand (Suit
is an enum
):
为了获得一些 Java 新流的经验,我一直在开发一个处理扑克牌的框架。这是我的代码的第一个版本,用于创建Map
包含手牌中每种花色的牌数(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:
这很有效,我很高兴。然后我重构,为“西装卡”和小丑创建单独的卡子类。所以这个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 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?
我不知道类型转换被认为是一个可执行语句。为什么这样做?为什么编译器需要它?
采纳答案by Joe C
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.
请记住,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 Foo
as 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)card
is 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 SuitCard
objects. 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 map
step looks ok. Even though it adds some overhead, usually it's not very significant.
如果您不喜欢使用第三方库,则涉及额外map
步骤的解决方案看起来不错。即使它增加了一些开销,通常也不是很重要。