[英]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.