前言:
今天跟大家分享一下java8的新特性之一—流,肯定有很多人見到過,但是我相信目前很多人還沒有廣泛的使用流—可能暫時沒有使用流的意識,或者說是使用的不熟練,如果真的是這樣,那麼今天分享的文章肯定會給你帶來巨大的衝擊,我們現在就來感受一下流的魅力。
在學習流的相關操作之前,希望先熟悉下lambda表達式和optional,這樣比較容易理解。
一、熱身運動
流可以幫助我們寫出更優雅且高性能的代碼,比如有這樣一個場景,比如你有一個女朋友(沒有的話就new一個吧),然後你女朋友的包包中裝了很多的東西,現在需要將你女朋友的包包中的東西都拿出來,如果我們使用傳統的迭代器,可能會是下面的場景:
你:媳婦兒,我們現在把你包包裏的東西拿出來,包包裏面有東西嗎?
女朋友:有,有小鏡子
你:好,把鏡子拿出來放到桌子上,還有嗎?
女朋友:有,充電寶
你:好,把充電寶拿出來放到桌子上吧,還有嗎?
女朋友:有,還有紙巾
你:好,把紙巾拿出來放到桌子上,還有嗎?
......
我們是不是感覺上面的對話看上去有點傻,其實這是我們在操作流或者數組的時候經常使用的邏輯,遍歷整個容器,然後做判斷或者操作。那麼如果我們使用java8的流操作將會是下面的場景:
你:媳婦兒,把你包包裏面的東西都拿出來放到桌子上
就是這麼簡單,我們下面來詳細的學習一下流的操作
上面的實例可能有些人會覺得集合也能用一些清空的操作啊,比如clear或者addAll等操作啊,上面的例子完全不能凸顯出流的特性,那接下來我們用一個代碼的例子來初步的體驗一下流會給我們什麼?
需求:需要從一張菜譜中找出所有低熱量的菜名,並且排序
package stream;
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
public enum Type{
MEAT,FISH,OTHER
}
}
如果我們使用java8之前的操作可能是下面的實現代碼
/***
* 獲取低熱量的菜品的名稱,並且按照熱量從高到底排序
* 熱量<400的認爲時低熱量
* java8之前的寫法
* @return
*/
public List<String> getSortLowCalories(List<Dish> dishes) {
if (dishes == null || dishes.isEmpty()) return null;
List<Dish> lowCaloriesSort = new ArrayList<>();
for (Dish dish : dishes) {
if (dish.getCalories() < 400) {
lowCaloriesSort.add(dish);
}
}
/*排序*/
Collections.sort(lowCaloriesSort, new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
return Integer.compare(o1.getCalories(), o2.getCalories());
}
});
List<String> lowDishNameSort = new ArrayList<>();
/*獲取名字*/
for (Dish dish : lowCaloriesSort) {
lowDishNameSort.add(dish.getName());
}
return lowDishNameSort;
}
先看下上面的代碼,簡單的分析一下:
- for循環遍歷了兩次,帶來性能問題
- List<Dish> lowCaloriesSort僅僅是一箇中間容器,即“垃圾變量”
- 代碼寫的很長,看上去不美觀
那麼我們引入java8代碼將會編程怎麼樣子的呢?
/***
* 使用java8流式的方式獲取低熱量的菜品的名稱,並且排序
* @param dishes
* @return
*/
public List<String> ortLowCaloriesFor8(List<Dish> dishes) {
List<String> lowDishNameSort = dishes.stream()
.filter(dish -> dish.getCalories() < 400)
.sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName)
.collect(Collectors.toList());
return lowDishNameSort;
}
看上去怎麼樣,是不是非常的精簡,看上去是不是很棒,如果你也是這麼認爲,那麼你可以繼續往下看看,java8的新特性其實相對比較簡單,一般都是一些操作性的內容,不像數據結構或併發編程、虛擬機等這些難以理解。
二、流的定義及描述
流:從支持數據處理操作的源生成的元素序列
看上去很青澀,很難理解,那就算了,我們儘量用一些比較容易理解的思維來轉換對流的理解,比如我們可以將流的操作比作數據庫的操作
如剛纔上面獲取低熱量的菜名,並且排序可以寫成下面的sql
Select name from dish where calorie < 400 order by calorie asc;
如果這個你能理解的話,那麼我相信你對流應該有一個初步的瞭解,流的操作就像一個管道一樣,流經過一個管道,那麼流就變成了另外一個流,如上面的例子可以用下圖來描述:
三、流的常用操作
上個類吧,代碼雖然較多,但是非常簡單
package stream;
import java.util.*;
import java.util.stream.Collectors;
public class StreamTest {
/***
* 獲取低熱量的菜品的名稱,並且按照熱量從高到底排序
* 熱量<400的認爲時低熱量
* java8之前的寫法
* @return
*/
public List<String> getSortLowCalories(List<Dish> dishes) {
if (dishes == null || dishes.isEmpty()) return null;
List<Dish> lowCaloriesSort = new ArrayList<>();
for (Dish dish : dishes) {
if (dish.getCalories() < 400) {
lowCaloriesSort.add(dish);
}
}
/*排序*/
Collections.sort(lowCaloriesSort, new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
return Integer.compare(o1.getCalories(), o2.getCalories());
}
});
List<String> lowDishNameSort = new ArrayList<>();
/*獲取名字*/
for (Dish dish : lowCaloriesSort) {
lowDishNameSort.add(dish.getName());
}
return lowDishNameSort;
}
/***
* 使用java8流式的方式獲取低熱量的菜品的名稱,並且排序
* @param dishes
* @return
*/
public List<String> ortLowCaloriesFor8(List<Dish> dishes) {
List<String> lowDishNameSort = dishes.stream()
.filter(dish -> dish.getCalories() < 400)
.sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName)
.collect(Collectors.toList());
return lowDishNameSort;
}
/************************************篩選和切片************************/
//1、篩選
/***
* 獲取是蔬菜的dish
* @param dishes
* @return
*/
public List<Dish> getVegeterianDish(List<Dish> dishes) {
if (dishes == null || dishes.isEmpty()) return null;
List<Dish> vegeterianDishes = new ArrayList<>();
for (Dish dish : dishes) {
if (dish.isVegetarian()) {
vegeterianDishes.add(dish);
}
}
return vegeterianDishes;
}
/***
* 使用java8獲取蔬菜的dish
* @param dishes
* @return
*/
public List<Dish> getVegeterianDishFor8(List<Dish> dishes) {
return dishes.stream().filter(Dish::isVegetarian)
.collect(Collectors.toList());
}
//2、distinct去重(和sql中的distinct一樣)
/***
* 獲取偶數,並且去重
* @param numbers
* @return
*/
public Set<Integer> filterOddRemoveDuplicate(List<Integer> numbers){
if (numbers == null || numbers.isEmpty()) return null;
Set<Integer> results = new HashSet<>();
for (Integer number : numbers){
if (number %2 == 0){
results.add(number);
}
}
return results;
}
/***
* 使用java8獲取偶數,並且去重
* @param numbers
* @return
*/
public List<Integer> filterOddRemoveDuplicateFor8(List<Integer> numbers){
return numbers.stream().filter(number -> number %2 == 0)
.distinct()
.collect(Collectors.toList());
}
//3、limit使用(和sql中的limit一樣)
/***
* 獲取高熱量的N道菜
* @param dishes
* @return
*/
public List<Dish> getNHightCalories(List<Dish> dishes,int limit){
if (dishes == null || dishes.isEmpty()) return null;
List<Dish> results = new ArrayList<>();
for (int i=0;i<limit;i++){
results.add(dishes.get(i));
}
return results;
}
/***
* 獲取高熱量的N道菜
* @param dishes
* @return
*/
public List<Dish> getThreeHightCaloriesFor8(List<Dish> dishes,int limit){
return dishes.stream().limit(limit).collect(Collectors.toList());
}
//4、skip使用(和支持skip的sql中的skip一樣)
/***
* 跳過N個,去K個dish,類似與是數據庫的分頁操作
* @param dishes
* @param skipN 跳過n
* @param k 取k個
* @return
*/
public List<Dish> skipNDish(List<Dish> dishes,int skipN,int k){
if (dishes == null || dishes.isEmpty()) return null;
/*假設數據合理,超過skipN+k個dish*/
List<Dish> results = new ArrayList<>();
for (int i = skipN;i<skipN+k;i++){
results.add(dishes.get(i));
}
return results;
}
/***
* 跳過N個,去K個dish,類似與是數據庫的分頁操作
* @param dishes
* @param skipN 跳過n
* @param k 取k個
* @return
*/
public List<Dish> skipNDishFor8(List<Dish> dishes,int skipN,int k){
return dishes.stream().skip(skipN)
.limit(k).collect(Collectors.toList());
}
/*****************************映射*********************************/
//5、map映射,類似數據中獲取一列的數據
/***
* 獲取菜品的名字
* @param dishes
* @return
*/
public List<String> getDishNames(List<Dish> dishes){
if (dishes == null || dishes.isEmpty()) return null;
List<String> results = new ArrayList<>();
for (Dish dish : dishes){
results.add(dish.getName());
}
return results;
}
/***
* 使用java8獲取菜品的名字
* @param dishes
* @return
*/
public List<String> getDishNamesFor8(List<Dish> dishes){
return dishes.stream().map(Dish::getName).collect(Collectors.toList());
}
//6、flatmap映射,Flatmap可以理解成將一些內容打散操作
/***
* 獲取字符中不同的單詞,如["hello","world"]
* 獲取的結果['h','e','l','o','w','r','d']
* @param words
* @return
*/
public Set<Character> getDifferentCharWord(List<String> words){
if (words == null || words.isEmpty()) return null;
Set<Character> charWords = new LinkedHashSet<>();
for (String word : words){
char[] chars = word.toCharArray();
for (char c : chars){
charWords.add(c);
}
}
return charWords;
}
/***
* 使用java8
* 獲取字符中不同的單詞,如["hello","world"]
* 獲取的結果['h','e','l','o','w','r','d']
* @param words
* @return
*/
public List<String> getDifferentCharWordFor8(List<String> words){
return words.stream().map(word->word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
}
/*******************查找和匹配******************************************/
//7、匹配
/***
* allMatch、anyMatch、noneMatch
* anyMatch:至少能匹配一個元素
* allMatch:都能匹配
* noneMatch:都不能匹配
* 比如是匹配是否有蔬菜
*/
/***
* 查找是否有蔬菜
* @param dishes
* @return
*/
public boolean existVegetarian(List<Dish> dishes){
if (dishes == null || dishes.isEmpty()) return false;
for (Dish dish : dishes){
if (dish.isVegetarian()) return true;
}
return false;
}
/***
* java8查找是否有蔬菜
* @param dishes
* @return
*/
public boolean existVegetarianFor8(List<Dish> dishes){
return dishes.stream().anyMatch(Dish::isVegetarian);
}
/***
* 判斷所有的菜是否健康,即所有菜的熱量都<400
* @param dishes
* @return
*/
public boolean isHeathy(List<Dish> dishes){
if (dishes == null || dishes.isEmpty()) return false;
for (Dish dish : dishes){
if (dish.getCalories() > 400) return false;
}
return true;
}
/***
* java8判斷所有的菜是否健康,即所有菜的熱量都<400
* @param dishes
* @return
*/
public boolean isHeathyFor8(List<Dish> dishes){
return dishes.stream().allMatch(dish -> dish.getCalories() < 400);
}
/***
* 使用java8的noneMatch判斷菜品是否健康
* @param dishes
* @return
*/
public boolean isHeathFor8NoneMath(List<Dish> dishes){
return dishes.stream().noneMatch(dish -> dish.getCalories() >= 400);
}
/***
* 使用anymath判斷菜品是否健康
* 即存在任意的熱量大於400的都是不健康的
* @param dishes
* @return
*/
public boolean isHeathFor8AnyMatch(List<Dish> dishes){
return !dishes.stream().anyMatch(dish -> dish.getCalories() >=400);
}
//8、查找
/***
* findAny:隨機任意找到一個
* findFirst:找到第一個
* 他們返回的Optional,這個也是java8的新特性
*/
/***
* java8之前實現獲取一道蔬菜
* @param dishes
* @return
*/
public Dish findAnyVegetarian(List<Dish> dishes){
if (dishes == null || dishes.isEmpty()) return null;
for (Dish dish : dishes){
if (dish.isVegetarian()){
return dish;
}
}
return null;
}
/***
* java8實現獲取一道蔬菜
* @param dishes
* @return
*/
public Dish findAnyVegetarianFor8(List<Dish> dishes){
return dishes.stream().filter(Dish::isVegetarian).findAny().orElse(null);
}
//
/***
* 找出第一個數的平方能被N整除的數字
* @return
*/
public Integer getFirstSquareDivisibleByN(List<Integer> numbers,int n){
if (numbers == null || numbers.isEmpty()) return null;
for (Integer number : numbers){
if (Math.pow(number,2) % n == 0){
return number;
}
}
return null;
}
/***
* 找出第一個數的平方能被N整除的數字
* @return
*/
public Integer getFirstSquareDivisibleByNFor8(List<Integer> numbers,int n){
Optional<Integer> result = numbers.stream().map(number->number * number)
.filter(number->number % n == 0)
.map(number->(int)Math.sqrt(number))
.findFirst();
return result.orElse(null);
}
/*****************規約************************/
//9、求和
/***
* java8之前實現求和
* @param numbers
* @return
*/
public int getSum(int[] numbers){
int result = 0;
for (int number : numbers){
result += number;
}
return result;
}
/***
* java8求和
* @param numbers
* @return
*/
public int getSumFor8(int[] numbers){
return Arrays.stream(numbers).reduce(0,Integer::sum);
}
//10、獲取最大值
/***
* 從數組中獲取最大值
* @param numbers
* @return
*/
public int getMax(int[] numbers){
if (numbers == null || numbers.length == 0) return Integer.MIN_VALUE;
int max = Integer.MIN_VALUE;
for (int number : numbers){
if (max < number) max = number;
}
return max;
}
/***
* 使用java8從數組中獲取最大值
* @param numbers
* @return
*/
public int getMaxFor8(int[] numbers){
return Arrays.stream(numbers).reduce(Integer::max).orElse(Integer.MIN_VALUE);
}
public static void main(String[] args) {
StreamTest streamTest = new StreamTest();
List<String> words = Arrays.asList("hello","world");
//System.out.println(streamTest.getDifferentCharWord(words));
//System.out.println(streamTest.getDifferentCharWordFor8(words));
//List<Integer> numbers = Arrays.asList(1,2,3,4,5,6);
//System.out.println(streamTest.getFirstSquareDivisibleByN(numbers,3));
//System.out.println(streamTest.getFirstSquareDivisibleByNFor8(numbers,3));
int[] numbers = {1,2,3,1000,55,333,112,333,555};
System.out.println(streamTest.getSum(numbers));
System.out.println(streamTest.getSumFor8(numbers));
}
}
上面的代碼很多沒有經過測試,可能有些有點問題,或者有更好的實現方式,大家可以略過這一點,重點看java8的代碼和java8之前進行對比,其實如果你看完了我相信對stream非常熱愛,甚至可能會回去改代碼了。
到這裏流的基本的操作算是介紹完了,這篇文章就寫道這裏吧,後面會更加深入的還會介紹一些流的更高級的使用。