java8 數值流 裝箱和拆箱講解

1、原始類型流特化

首先引入兩個概念

原始類型:int、double、byte、char

引用類型:Integer、Byte、Object、List

在Java中,①將原始類型轉換爲對應的引用類型的機制,這個機制叫做裝箱。②將引用類型轉換爲對應的原始類型,叫做拆箱。

但是在java中裝箱和拆箱是自動完成的,例如:

 

List<Integer> list = new ArrayList<>();
    for(int i = 0 ; i < 100 ;i++){
    list.add(i);
}

但是這麼做(int被裝箱成Integer)在性能方面是要付出代價的,裝箱的本質就是將原始類型包裹起來,並保存在堆裏。因此裝箱後的值需要更多的內存,並需要額外的內存搜索來獲取被包裹的原始值。

 

言歸正傳

 

重點:java8 引入了三個原始類型特化流來解決這個問題;IntStream、DoubleStream、LongStream 分別將流中元素特化爲int、double、long,從而避免了暗含裝箱的成本。每個接口都帶來了常用數值歸約的新方法,例如sum、max、min

案例變量:menu爲一個菜單列表(list),內含有name(名字)、calories(熱量)、type(類型)屬性。

案例說明:從menu流中,求出菜單中所有菜的熱量和

 

int calories = menu.stream()
        .mapToInt(Dish::getCalories)
        .sum();

mapToInt 和 map 對比

首先看兩個源碼

map方法

 <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    在看其 map 內引用的 Funcation<T,R> 函數式接口內抽象方法 apply

  R apply(T t);

推導結論:可以看出如果 menu 內,calories 類型定義 Integer 時,我們案例返回的是  int  類型,這其中會將 Integer 轉化爲 int 類型,這其中就帶出了一個裝箱的成本。

mapToInt方法

    IntStream mapToInt(ToIntFunction<? super T> mapper);

    在看其 mapToInt 內引用的  ToIntFuncation<T> 函數式接口內抽象方法 applyAsInt

  int applyAsInt(T value);

推導結論:可以看出案例中我們需要返回的是 int 類型,並且 mapToInt 返回的也是int類型,這其中是省去了一個裝箱的成本。
 

 

2、數值範圍

生成某一範圍的數字流。

加入你想生成 1 到 100 的數字流。

你有兩種方式可以選擇,

第一種:使用 IntStream 和 LongStream 靜態方法,幫助生成這種範圍: range  和 rangeClosed 。這兩個方法都是第一個參數接收起始值,第二個參數結束值。

案例說明:輸出 1 到 100 中所有偶數

案例解決第一步:生成包含 1 到 100 所有整數的流。(用到 IntStream 和 rangeClosed )

案例解決第二步:對省的流進行過濾,過濾出所有的偶數。(用到filter)

案例解決第三步:輸出過濾後的結果(用到 forEach 終端操作 輸出符合條件的結果)

案例代碼實現:  

IntStream.rangeClosed(1,100).filter(n -> n % 2 == 0).forEach(System.out::println);

 

數值流的應用:勾股數

實現思路:

第一步:生成a   爲  1 到 100 範圍內的所有數字。使其生成一個流(通過boxed、和flatMap)

第二步:生成b   爲 1 到 100 範圍內的所有數字。並將 a 、 b 、以及sqrt(a*a+b*b) 生成特化流

第三步:對生成的特化流進行過濾,過濾出 sqrt(a*a+b*b) 爲整數的組合,這裏過濾出的結果集就爲勾股數組。

 

IntStream.rangeClosed(1,100).boxed()
        .flatMap(a1->
    IntStream.rangeClosed(1,100).mapToObj(
    b -> new double[]{a1,b,Math.sqrt(a1*a1+b*b)})
    .filter(t -> t[2] % 1 == 0)).limit(5).forEach(c ->System.out.println(c[0]+","+c[1]+","+c[2]));

上面的案例輸出了0 ~ 100 之間可以組成勾股數的組合

輸出:

3.0,4.0,5.0
4.0,3.0,5.0
5.0,12.0,13.0
6.0,8.0,10.0
7.0,24.0,25.0

知識點解析

解析boxed應用

正確的

A           IntStream.range(0, 10).mapToObj(i->new Product()).collect(Collectors.toList());

報錯的

B           IntStream.range(0, 10).collect(Collectors.toList());

正確的

C           IntStream.range(0,10).boxed().collect(Collectors.toList());

通過上面正確案例和錯誤案例你可以清楚的思考到,可能是因爲裝箱的問題。

一語概括:在Java中,泛型的引用只能綁定到允許引用的對象類型,而(int、byte等)原始類型不屬於對象類型,但是每一個原始類型都是有對應的引用類型,在上方已經對原始類型和引用類型進行了講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章