淺聊java8中數(shù)值流的使用
簡介
java8為我提供的簡單快捷的數(shù)值流計算API,本文就基于幾個常見的場景介紹一下數(shù)值流API的使用。
基礎(chǔ)示例
我們以一個食物熱量計算的功能展開演示,如下所示,可以看到Dish類它記錄了每一個食物的名稱、熱量、類型等信息:
public class Dish {
/**
* 名稱
*/
private final String name;
/**
* 是否是素食
*/
private final boolean vegetarian;
/**
* 卡路里
*/
private final int calories;
/**
* 類型
*/
private final Type type;
//類型枚舉 分別是是:肉類 魚類 其他
public enum Type {MEAT, FISH, OTHER}
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
//...... get set
}
基于這個食物類,我們給出一個食物類的集合作為模擬數(shù)據(jù):
public static final List<Dish> menuList =
Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 400, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
我們希望計算出這個菜肴集合的總熱量,我們可能會這樣寫:
public static void main(String[] args) {
int total = menuList.stream()
//獲取每個食物的卡路里
.map(Dish::getCalories)
//調(diào)用reduce,從0開始累加每個食物的熱量
.reduce(0, Integer::sum);
System.out.println(total);
}
輸出結(jié)果如下:
4300
盡管它盡可能的簡潔并計算出了總熱量,但是它存在許多隱患,首先時map時,它會將基本類型的calories裝箱成Integer,這一點我們查看map的返回值即可知曉。
Stream<Integer> integerStream = menuList.stream()
//獲取每個食物的卡路里
.map(Dish::getCalories);
因為拿到的是包裝類的流,調(diào)用reduce進行數(shù)值計算時,有需要對其進行拆箱,拆箱時就會調(diào)用到Integer的intValue方法:
public int intValue() {
return value;
}
所以若在大量數(shù)值計算的情況下,頻繁的拆箱和裝箱勢必導(dǎo)致程序的執(zhí)行效率低下。

特值流
那么有沒有什么辦法可以保證在數(shù)據(jù)收集的時候避免頻繁裝箱和拆箱呢?答案是特化流,就以本案例來說,我們在數(shù)值收集的時候直接調(diào)用mapToInt方法,通過該方法即可得到每一個數(shù)值的特值流IntStream,隨后我們直接調(diào)用特值流計算方法sum即可完成熱量統(tǒng)計:

對應(yīng)的代碼示例如下:
public static void main(String[] args) {
int total = menuList.stream()
//將每一個卡路里轉(zhuǎn)換為特值流IntStream
.mapToInt(Dish::getCalories)
//將所有數(shù)值累加
.sum();
System.out.println(total);
}
最終輸出結(jié)果也是4300:
4300
相較于reduce方法,特值流提供了更多更方便的計算API:
average:計算所有數(shù)值的平均數(shù)。count:獲取數(shù)值總數(shù)。max:獲取收集數(shù)據(jù)中的最大值。min:獲取收集數(shù)據(jù)中的最小值。
特化流還原會原始流
有時候我們希望這些特化流轉(zhuǎn)為原始流即包裝類的流,那么我們可直接調(diào)用boxed方法完成對特值流的裝箱:
public static void main(String[] args) {
Stream<Integer> integerStream = menuList.stream()
//拿到所有數(shù)值的特值流
.mapToInt(Dish::getCalories)
//將所有特值流裝箱
.boxed();
//輸出特值流對象的數(shù)值
integerStream.forEach(i -> System.out.println(i));
}
特化流空數(shù)值問題
我們都知道特化流可以直接獲取收集到數(shù)值的最大值或者最小值,我們假設(shè)這樣一個場景,食物類對象的卡路里字段為Integer :
private final Integer calories;
并且我們食物類的集合為空:
public static final List<Dish> menuList = new ArrayList<>();
面對可能存在的空結(jié)果問題,要如何解決呢?
實際上java8已經(jīng)考慮到這個問題了,當(dāng)我們調(diào)用max等計算API獲取結(jié)果時,它實際返回的對象是OptionalInt,該對象提供了各種API用于判斷數(shù)值是否為空,當(dāng)我們最大值為空,就直接返回1時,我們可以直接使用orElse方法:
public static void main(String[] args) {
OptionalInt max = menuList.stream()
.mapToInt(Dish::getCalories)
.max();
//不存在最大值時,直接返回1
System.out.println(max.orElse(0));
}
亦或者我們需要判斷是否存在最大值時,可以直接調(diào)用isPresent方法:
public static void main(String[] args) {
OptionalInt max = menuList.stream()
.mapToInt(Dish::getCalories)
.max();
//若存在最大值直接返回true
System.out.println(max.isPresent());
}
數(shù)值流的范圍操作
我們希望統(tǒng)計1-100之間的偶數(shù)數(shù)量,在java8之前,你可能會這樣做:
for循環(huán)1-100。- 判斷是否是偶數(shù)。
- 如果是偶數(shù),則臨時變量
count自增一下。
而java8的步驟則精簡許多:
- 基于特值流生成1-100全閉區(qū)間數(shù)據(jù)。
- 過濾出偶數(shù)。
- 調(diào)用
count進行統(tǒng)計。
public static void main(String[] args) {
//生成1-100全閉區(qū)間數(shù)據(jù)
long count = IntStream.rangeClosed(1, 100)
//過濾出偶數(shù)
.filter(i -> i % 2 == 0)
//計算統(tǒng)計結(jié)果
.count();
System.out.println(count);
}
輸出結(jié)果:
50
當(dāng)然,如果你要生成左閉右開即1-99,則可以調(diào)用range方法生成:
IntStream.rangeClosed(1, 99)
數(shù)值流的應(yīng)用——勾股數(shù)
現(xiàn)在我們來寫一個獲取1-100以內(nèi)前3個勾股數(shù)的小功能。由公式:
a^2 + b^2=c^2
可知,要想得到勾股數(shù),我們只需判斷a^2 + b^2的和再開根號是否可以被整除,即:
Math.sqrt(a * a + b * b) % 1 == 0
所以我們可以按照下面這樣的步驟執(zhí)行:
- 創(chuàng)建1-100全閉區(qū)間作為第一條邊a。
- 為避免計算的勾股數(shù)重復(fù),出現(xiàn)[
3,4,5],[4,3,5]這種情況,我們的第二條邊b范圍為a-100。 - 拿著a和b,計算這兩個數(shù)值的平方和再開根號看看是否為整數(shù)。
- 將開根號結(jié)果為整數(shù)的結(jié)果生成數(shù)組。
- 獲取前3個這樣的數(shù)組。

所以我們寫出下面這段代碼,需要注意的是筆者在生成b的時候用到了flatMap,原因很簡單,因為生成a時boxed返回的對象是Stream<Integer>,假如把這個流直接用map和b進行映射操作的話,最終結(jié)果只能是[Stream<Integer>,Integer,Integer],所以我們需要使用flatMap將a進行扁平化從而得到一個Integer:
public static void main(String[] args) {
//生成a
Stream<int[]> result = IntStream.rangeClosed(1, 100).boxed()
//基于a的范圍生成 a-100范圍的b,并過濾出平方再開方后可以整除的b,構(gòu)成數(shù)組
.flatMap(a -> IntStream.rangeClosed(a, 100).filter(b -> Math.sqrt(a * a + b * b) % 1 == 0).boxed().map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)}))
//取前3個
.limit(3);
//打印輸出
result.forEach(r -> System.out.println(r[0] + " " + r[1] + " " + r[2]));
}
最終輸出結(jié)果如下:
3 4 5
5 12 13
6 8 10
但是這種寫法不夠好,可以看到我們得到合適a和b時,還需要手動調(diào)用boxed將其還原為原始流,再用map映射為數(shù)組,這樣實在太麻煩了。
還記得我們特化流還原為原始流的一個方法mapToxxx方法嗎?如果我們希望將其轉(zhuǎn)為數(shù)組,我們在得到a和b之后,直接調(diào)用mapToObj,代碼一步到位:
public static void main(String[] args) {
//生成a
Stream<int[]> result = IntStream.rangeClosed(1, 100).boxed()
//基于a的范圍生成 a-100范圍的b,并過濾出平方再開方后可以整除的b,構(gòu)成數(shù)組
.flatMap(a -> IntStream.rangeClosed(a, 100).filter(b -> Math.sqrt(a * a + b * b) % 1 == 0).mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)}))
//取前3個
.limit(3);
//打印輸出
result.forEach(r -> System.out.println(r[0] + " " + r[1] + " " + r[2]));
}
以上就是淺聊java8中數(shù)值流的使用的詳細內(nèi)容,更多關(guān)于java8數(shù)值流的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring中AOP概念與兩種動態(tài)代理模式原理詳解
AOP是面向切面編程的技術(shù),AOP基于IoC基礎(chǔ),是對OOP的有益補充,流行的AOP框架有Sping AOP、AspectJ,這篇文章主要給大家介紹了關(guān)于Spring中AOP概念與兩種動態(tài)代理模式原理的相關(guān)資料,需要的朋友可以參考下2021-10-10
SpringBoot中restTemplate請求存在亂碼問題的解決方法
這篇文章主要介紹了SpringBoot中restTemplate請求存在亂碼問題的解決方法,文中有相關(guān)的圖文和代碼示例供大家參考,對大家的解決問題有一定的幫助,需要的朋友可以參考下2024-11-11
springboot內(nèi)置tomcat調(diào)優(yōu)并發(fā)線程數(shù)解析
這篇文章主要介紹了springboot內(nèi)置tomcat調(diào)優(yōu)并發(fā)線程數(shù)解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Springboot整合JPA配置多數(shù)據(jù)源流程詳解
這篇文章主要介紹了Springboot整合JPA配置多數(shù)據(jù)源,JPA可以通過實體類生成數(shù)據(jù)庫的表,同時自帶很多增刪改查方法,大部分sql語句不需要我們自己寫,配置完成后直接調(diào)用方法即可,很方便2022-11-11
IDEA 2020.3 更新了機器學(xué)習(xí)都整上了
IDEA 歡迎窗口全新升級,首頁增加三個選項卡,一個用于設(shè)置 IDE 界面的 Customize,一個用于插件安裝的 Plugins,一個于訪問幫助和學(xué)習(xí)資源的 Learn IntelliJ IDEA,另外包括之前用于管理項目的 Projects,需要的朋友可以參考下2020-12-12
SpringBoot JavaMailSender發(fā)送郵件功能
這篇文章主要為大家詳細介紹了SpringBoot JavaMailSender發(fā)送郵件功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04

