淺聊java8中數(shù)值流的使用
簡(jiǎn)介
java8為我提供的簡(jiǎn)單快捷的數(shù)值流計(jì)算API,本文就基于幾個(gè)常見的場(chǎng)景介紹一下數(shù)值流API的使用。
基礎(chǔ)示例
我們以一個(gè)食物熱量計(jì)算的功能展開演示,如下所示,可以看到Dish類它記錄了每一個(gè)食物的名稱、熱量、類型等信息:
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
}
基于這個(gè)食物類,我們給出一個(gè)食物類的集合作為模擬數(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)
);
我們希望計(jì)算出這個(gè)菜肴集合的總熱量,我們可能會(huì)這樣寫:
public static void main(String[] args) {
int total = menuList.stream()
//獲取每個(gè)食物的卡路里
.map(Dish::getCalories)
//調(diào)用reduce,從0開始累加每個(gè)食物的熱量
.reduce(0, Integer::sum);
System.out.println(total);
}
輸出結(jié)果如下:
4300
盡管它盡可能的簡(jiǎn)潔并計(jì)算出了總熱量,但是它存在許多隱患,首先時(shí)map時(shí),它會(huì)將基本類型的calories裝箱成Integer,這一點(diǎn)我們查看map的返回值即可知曉。
Stream<Integer> integerStream = menuList.stream()
//獲取每個(gè)食物的卡路里
.map(Dish::getCalories);
因?yàn)槟玫降氖前b類的流,調(diào)用reduce進(jìn)行數(shù)值計(jì)算時(shí),有需要對(duì)其進(jìn)行拆箱,拆箱時(shí)就會(huì)調(diào)用到Integer的intValue方法:
public int intValue() {
return value;
}
所以若在大量數(shù)值計(jì)算的情況下,頻繁的拆箱和裝箱勢(shì)必導(dǎo)致程序的執(zhí)行效率低下。

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

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

所以我們寫出下面這段代碼,需要注意的是筆者在生成b的時(shí)候用到了flatMap,原因很簡(jiǎn)單,因?yàn)樯蒩時(shí)boxed返回的對(duì)象是Stream<Integer>,假如把這個(gè)流直接用map和b進(jìn)行映射操作的話,最終結(jié)果只能是[Stream<Integer>,Integer,Integer],所以我們需要使用flatMap將a進(jìn)行扁平化從而得到一個(gè)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個(gè)
.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時(shí),還需要手動(dòng)調(diào)用boxed將其還原為原始流,再用map映射為數(shù)組,這樣實(shí)在太麻煩了。
還記得我們特化流還原為原始流的一個(gè)方法mapToxxx方法嗎?如果我們希望將其轉(zhuǎn)為數(shù)組,我們?cè)诘玫絘和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個(gè)
.limit(3);
//打印輸出
result.forEach(r -> System.out.println(r[0] + " " + r[1] + " " + r[2]));
}
以上就是淺聊java8中數(shù)值流的使用的詳細(xì)內(nèi)容,更多關(guān)于java8數(shù)值流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用kafka如何選擇分區(qū)數(shù)及kafka性能測(cè)試
這篇文章主要介紹了使用kafka如何選擇分區(qū)數(shù)及kafka性能測(cè)試,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java線程中斷及線程中斷的幾種使用場(chǎng)景小結(jié)
在并發(fā)編程中,合理使用線程中斷機(jī)制可以提高程序的魯棒性和可維護(hù)性,本文主要介紹了Java線程中斷及線程中斷的幾種使用場(chǎng)景小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
SpringBoot項(xiàng)目創(chuàng)建單元測(cè)試的流程步驟
在日常開發(fā)的過程中,對(duì)自己的代碼進(jìn)行單元測(cè)試是個(gè)非常重要的過程,一方面可以最小范圍的針對(duì)一個(gè)方法進(jìn)行測(cè)試,提高測(cè)試的簡(jiǎn)便性以及測(cè)試的成本,本篇文章主要是為了總結(jié)一下如何優(yōu)雅的在Springboot項(xiàng)目中使用單元測(cè)試去測(cè)試功能,需要的朋友可以參考下2024-11-11
springboot接收excel數(shù)據(jù)文件去重方式
文章主要介紹了如何在Spring?Boot中實(shí)現(xiàn)文件上傳并入庫(kù)的功能,包括讀取Excel文件、生成Entity對(duì)象、使用MergeInto語句進(jìn)行數(shù)據(jù)庫(kù)操作以及注意事項(xiàng)2024-12-12
SpringBoot集成thymeleaf瀏覽器404的解決方案
前后端不分離的古早 SpringMVC 項(xiàng)目通常會(huì)使用 thymeleaf 模板引擎來完成 html 頁(yè)面與后端接口之間的交互,如果要將項(xiàng)目架構(gòu)升級(jí)成 SpringBoot , thymeleaf 也可以照常集成,但有時(shí)候會(huì)踩到一些坑,所以本文給大家介紹了SpringBoot集成thymeleaf瀏覽器404的解決方案2024-12-12
ElasticSearch學(xué)習(xí)之多條件組合查詢驗(yàn)證及示例分析
這篇文章主要為大家介紹了ElasticSearch 多條件組合查詢驗(yàn)證及示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Java實(shí)現(xiàn)瀏覽器大文件上傳的示例詳解
文件上傳是許多項(xiàng)目都有的功能,用戶上傳小文件速度一般都很快,但如果是大文件幾個(gè)g,幾十個(gè)g的時(shí)候,上傳了半天,馬上就要完成的時(shí)候,網(wǎng)絡(luò)波動(dòng)一下,文件又要重新上傳,所以本文給大家介紹了Java實(shí)現(xiàn)瀏覽器大文件上傳的示例,需要的朋友可以參考下2024-07-07
基于Servlet實(shí)現(xiàn)技術(shù)問答網(wǎng)站系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了基于Servlet實(shí)現(xiàn)技術(shù)問答網(wǎng)站系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04

