Java8 流式編程
流是一系列與特定存盤機制無關的元素——實際上,流并沒有“存盤”之說,
使用流,無需迭代集合中的元素,就可以提取/操作特定的元素
假設我要生成一個隨機序列,范圍在5到100之間,不重復,隨機生成7個數字,而且要排序,最后輸出序列,可以這么做:
public static void main(String[] args) {
new Random(47) // 種子
.ints(5, 100)
.distinct()
.limit(7)
.sorted()
.forEach(System.out::println);
}
整個程序是一個作業流,它不需要單獨提取出這個序列在進行操作,
如果要實作剛剛的功能,而不使用流,就可能會是這樣子:
public static void main(String[] args) {
Random random = new Random(47);
SortedSet<Integer> set = new TreeSet<>();
while (set.size() < 7){
int r = random.nextInt(100);
if(r >= 5){
set.add(r);
}
}
System.out.println(set);
}
流操作的型別有三種:
- 創建流
- 修改流元素
- 消費流元素(終端操作,列印/收集元素等等)
.forEach().sum()
創建流
可以用Steam.of將物件陣列/一組元素轉換為 流
String[] arr = {"C++ ", "Python ", "Java"};
Stream.of(arr).forEach(System.out::print);
Stream.of("C++ ", "Python ", "Java ").forEach(System.out::print);
每個集合也可以用.stream()來產生一個流
List<String> strList = Arrays.asList("C++ ", "Python ", "Java ");
strList.stream().forEach(System.out::print);
// C++ Python Java
對于原始資料型別的陣列/串列,可以用Arrays.stream()
- 如果用
Steam.of(),輸出的是一個地址
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Arrays.stream(arr).forEach(System.out::println); // 輸出元素1 2 3 4 ...
Stream.of(arr).forEach(System.out::println); // 輸出地址 [I@214c265e
創建亂數流
生成4個隨機整數,輸出
ints(n):控制流的大小為4,生成4個整數,回傳一個IntStreamforEach(System.out::println):列印每個元素
Random random = new Random(47); // 47是引數,種子
random.ints(4).forEach(System.out::println);
生成5個隨機整數,只取前3個整數
Random random = new Random();
random.ints(5).limit(3).forEach(System.out::println);
創建6個隨機整數,范圍在10~20之間
ints(m,n):生成[m,n)之間的整數,回傳一個IntStream- 用了
ints(m,n)的方法,要加limit(),不然就是無限回圈
Random random = new Random();
random.ints(10,20).limit(6).forEach(System.out::println);
也可以用init(streamSize,numberOrigin,numberBound)來創建:
- 第一個引數是流的大小,第二第三個引數是表示亂數的范圍
- 下面這段代碼就是:生成6個隨機整數,范圍在10~20之間
Random random = new Random();
random.ints(6,10,20).forEach(System.out::println);
創建整形序列流
假設我要創建一個[1,100]的連續序列,求它的和,可以用 IntStream 提供的 range(m,n)方法
public static void main(String[] args) {
// 1-100的序列陣列
int[] intsArr = IntStream.range(1, 101).toArray();
int sum = Arrays.stream(intsArr).sum();
System.out.println(sum);
}
可以寫的再簡單一點:
public static void main(String[] args) {
// 1-100的序列陣列
int sum = IntStream.range(1, 101).sum();
System.out.println(sum);
}
對比傳統的方法,就會覺得用 流 會方便很多:
// 傳統方法
public static void main(String[] args) {
int sum = 0;
for(int i = 1; i <= 100;++i){
sum += i;
}
System.out.println(sum);
}
使用Stream.generate()創建
Stream.generate() 回傳無限流,我們可以自定義生成的元素
生成5個隨機整數
Stream.generate(() -> {
return new Random().nextInt();
}).limit(5).forEach(System.out::println);
也可以簡寫成:
Stream.generate(new Random()::nextInt).limit(5).forEach(System.out::println);
使用Stream.iterate()創建
方法原型:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
// ...
}
第一個引數:種子
第二個引數:方法(lambda運算式)
將 種子 傳遞給 方法,方法的運行結果 作為流的下一個元素,添加到流中,存盤起來,作為下次呼叫iterate()的第二個引數
用iterate()生成斐波那契數列
iterate()只記憶結果
public class Generator {
private int x = 0;
Stream<Integer> numbers(){
return Stream.iterate(1,(i)->{
int res = x + i;
x = i;
return res;
});
}
public static void main(String[] args) {
// 生成前10項
new Generator().numbers().limit(10).forEach(System.out::println);
// 生成第10到20項
new Generator()
.numbers()
.skip(10) // 過濾前10個
.limit(10) // 然后取10個
.forEach(System.out::println);
}
}
Arrays.stream() 和 Stream.of()
Arrays 類中含有一個名為 stream() 的靜態方法用于把 int/long/double陣列 轉換成為流,
int、double、long等基本型別的陣列,用 Arrays.stream(陣列名) 進行流的操作
如果是String(String是一個物件),可以用 Stream.of(物件陣列名) 進行流的操作
Stream.of() 和 Arrays.stream() 是有區別的:
- 如果是基本型別的物件陣列(如
Integer、Long),兩個方法是一樣的結果
Integer[] intArr = {1, 2, 3, 4};
Arrays.stream(intArr).forEach(System.out::print); // 1234
Stream.of(intArr).forEach(System.out::print); // 1234
- 如果是基本型別的陣列(如
int、long),兩個方法回傳的結果不一樣
int[] intArr = {1, 2, 3, 4};
Arrays.stream(intArr).forEach(System.out::print); // 1234
Stream.of(intArr).forEach(System.out::print); // [I@214c265e
這是因為int[]傳入Stream.of()會被當作一個物件,而傳入Arrays.stream()會被當成一個陣列
流的中間操作
什么是流的中間操作?就是修改流中的元素,回傳一個修改后的Stream,
一個流創建之后有6個元素,我跳過前3個,只取最后3個,那中間操作就是 “跳過前三個”
peek() 跟蹤和除錯
peek() 的目的是:無修改的查看流中的元素
peek()引數可接收lambda運算式
當流中的一個元素通過管道的時候,就會呼叫一次peek()
例1
public static void main(String[] args) {
String[] str = {"java ", "c++ ", "php ", "dart "};
Stream.of(str)
.skip(1)
.peek(System.out::print) // 輸出當前流的情況
.map(s -> "第1次變化:" + s) // 映射,修改str中字串的值
.peek(System.out::print)
.forEach(System.out::println);
}
例1-輸出結果:
c++ 第1次變化:c++ 第1次變化:c++
php 第1次變化:php 第1次變化:php
dart 第1次變化:dart 第1次變化:dart
例2
public static void main(String[] args) {
String[] str = {"java ", "c++ ", "php ", "dart "};
Stream.of(str)
.skip(1)
.peek(System.out::print)
.map(String::toUpperCase) // 映射,將流中的元素全轉成大寫
.forEach(System.out::print);
}
例2-輸出結果
c++ C++ php PHP dart DART
sorted() 排序
sorted()接收一個lambda運算式,也可以接收一個 比較器(sorted()預設了一些默認的比較器)
基本型別陣列排序
我查了資料,發現 基本型別的陣列的sorted()不能傳比較器,它們的包裝類就可以
public static void main(String[] args) {
// 基本型別陣列
int[] arr = {1, 18, 12, 16, 4};
// 默認正序
Arrays.stream(arr).sorted().forEach(System.out::println); // 1 4 12 16 18
}
物件/包裝類陣列排序
public static void main(String[] args) {
// Integer型別陣列
Integer[] integerArr = {1, 18, 12, 16, 4};
// 正序
Arrays.stream(integerArr).sorted().forEach(System.out::println); // 1 4 12 16 18
// 逆序
Arrays.stream(arr).sorted(Comparator.reverseOrder()).forEach(System.out::println); // 18 16 12 4 1
}
集合排序
public static void main(String[] args) {
// List 集合
List<Integer> list = new LinkedList<>(Arrays.asList(1,18,12,16,4));
// 正序
list.stream().sorted().forEach(System.out::println); // 1 4 12 16 18
// 逆序
list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println); // 18 16 12 4 1
}
distinct() 去重
消除流中重復的元素
public static void main(String[] args) {
// 陣列
Integer[] arr = {1, 1, 1, 2, 2};
Arrays.stream(arr).distinct().forEach(System.out::println); // 1 2
}
filter(Predicate) 過濾器
filter()接收到引數是一個函式,如果函式回傳 true,則保留這些元素,回傳false 則洗掉這些元素
假定有一個陣列,保留其中的奇數:
public class Main {
public static void main(String[] args) {
// 陣列
Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
Arrays.stream(arr).filter(Main::Odd).forEach(System.out::println); // 1 3 5 7
}
// 奇數回傳true
public static Boolean Odd(int number) {
return number % 2 == 1;
}
}
同樣的,filter()也接收正則運算式
public static void main(String[] args) {
// 陣列
Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
Arrays.stream(arr).filter(num -> num % 2 == 1).forEach(System.out::println);
}
map()相關 - 映射到元素
map(Function):將函式操作應用在輸入流的元素中,并將回傳值傳遞到輸出流中,mapToInt(ToIntFunction):操作同上,但結果是 IntStream,mapToLong(ToLongFunction):操作同上,但結果是 LongStream,mapToDouble(ToDoubleFunction):操作同上,但結果是 DoubleStream,
public static void main(String[] args) {
// 陣列
Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
Arrays.stream(arr)
.map(s -> s * 3) // 每個元素 × 3
.forEach(System.out::println); // 2 4 9 16 10 12 14
}
skip(long n) 跳過前n個元素
skip(long n)可以省略流的前n個元素
public static void main(String[] args) {
int[] arr= {1,2,3,4,5};
// 省略前 3 個
Arrays.stream(arr).skip(3).forEach(System.out::println); // 4 5
}
limit(long n) 保留前n個元素
limit(long n) 可以保留前n個元素,其他的不要
public static void main(String[] args) {
int[] arr= {1,2,3,4,5};
// 保留前 3 個
Arrays.stream(arr).limit(3).forEach(System.out::println); // 1 2 3
}
parallel() 流的并行處理
parallel() 可實作多處理器并行操作,實作原理為將流分割為多個(通常數目為 CPU 核心數)并在不同處理器上分別執行操作,
使用場景(前提是確保執行緒的安全):
- 將檔案下載,保存到服務器
- 遍歷
假設這樣一個場景:有一個商品的分類ld,要根據分類Id,去資料庫找具體的資訊(IO操作)
傳統的遍歷
- 運行時間:732毫秒
public static void main(String[] args) {
List<Category> list = getCategoryIdList(); // 獲取商品分類的Id
// 普通的遍歷
long start = System.currentTimeMillis();
for (Category category : list) {
System.out.println(getById(category.id));
}
long end = System.currentTimeMillis();
System.out.println("普通遍歷 運行時間:" + (end - start) + "毫秒");
}

使用 parallel()遍歷
-
遍歷的結果是無序的
-
運行時間:607毫秒
public static void main(String[] args) {
List<Category> list = getCategoryIdList(); // 獲取商品分類的Id
// 并行遍歷
long start = System.currentTimeMillis();
list.stream().parallel().forEach(category -> {
// IO 操作
System.out.println(getById(category.id)); // 根據分類Id,查詢具體資訊
});
long end = System.currentTimeMillis();
System.out.println("parallel() 運行時間:" + (end - start) + "毫秒");
}

使用 Java8 的 parallel 可以加快某些操作的速度,但如果是一些簡單的操作,那就得不償失了
此外,parallel() 遍歷的結果是無序的
流的終端操作
終端操作:獲取流的最終結果,無法再往后傳遞流,比如列印、獲取集合、查找,都算是終端操作
回傳 Optional 物件
Optional 類 隱藏了可能存在空指標的不確定性
一些終端操作,會回傳一個Optional 物件,因為這些操作,不能保證預期結果一定存在,也就是隱藏了空指標的資訊,如果流是空的,那就會回傳Option.empty
-
findFirst()回傳一個包含第一個元素的 Optional 物件,如果流為空則回傳 Optional.empty -
findAny()回傳包含任意元素的 Optional 物件,如果流為空則回傳 Optional.empty -
max()和min()回傳一個包含最大值或者最小值的 Optional 物件,如果流為空則回傳 Optional.emptyreduce()不再以identity形式開頭,而是將其回傳值包裝在 Optional 中,(
identity物件成為其他形式的reduce()的默認結果,因此不存在空結果的風險)
findFirst()
public static void main(String[] args) {
String [] str1 = {"java","python","c++"};
String [] str2 = {};
System.out.println(Stream.of(str1).findFirst()); // Optional[java]
System.out.println(Stream.of(str2).findFirst()); // Optional.empty
}
findAny()
public static void main(String[] args) {
String [] str1 = {"java","python","c++"};
String [] str2 = {};
System.out.println(Stream.of(str1).findAny()); // Optional[java]
System.out.println(Stream.of(str2).findAny()); // Optional.empty
}
max() 和 min()
public static void main(String[] args) {
Integer[] arr1 = {1, 2, 3, 4, 5};
Integer[] arr2 = {};
System.out.println(Stream.of(arr1).max(Integer::compareTo)); // Optional[5]
System.out.println(Stream.of(arr1).min(Integer::compareTo)); // Optional[5]
System.out.println(Stream.of(arr2).min(Integer::compareTo)); // Optional.empty
System.out.println(Stream.of(arr2).max(Integer::compareTo)); // Optional.empty
}
解包 Optional:
isPresent():判斷 Optional 物件中是否包含元素ifPresent(Consumer):如果 Optional物件中包含元素,就呼叫 Consumer方法(lambda運算式)get():獲取 Optional物件的 元素orElse(otherObject):如果值存在則直接回傳,否則生成 otherObject,orElseGet(Supplier):如果值存在則直接回傳,否則使用 Supplier 函式生成一個可替代物件,orElseThrow(Supplier):如果值存在直接回傳,否則使用 Supplier 函式生成一個例外,
toArray() 回傳陣列
-
toArray():將流按照陣列回傳 -
toArray(T[] a):生成自定義型別的陣列
生成隨機整型陣列:
public static void main(String[] args) {
int[] arr = new Random().ints(5).toArray(); // 生成生成5個int型,轉換為int陣列
}
用toArray(T[] a)將集合轉成陣列
public static void main(String[] args) {
List<String> list = new LinkedList<>();
list.add("java ");
list.add("python");
String[] str = (String[]) list.toArray(new String[0]);
Stream.of(str).forEach(System.out::print); // java python
}
forEach() 遍歷
-
forEach(Consumer)常見如System.out::println作為 Consumer 函式, -
forEachOrdered(Consumer): 保證forEach按照原始流順序操作,(parallel()操作之后是無序的,用這個可以強制保持原始流的順序)
兩個函式都接收lambda運算式
使用forEach遍歷陣列:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
// 接收lambda運算式
// i 就是陣列中的元素
Arrays.stream(arr).forEach(i -> {
// do something...
});
傳統方法遍歷陣列:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
for(Integer i : arr){
// do something...
}
collect() 回傳集合
-
collect(Collector):使用 Collector 收集流元素到結果集合中, -
collect(Supplier, BiConsumer, BiConsumer):同上,第一個引數 Supplier 創建了一個新的結果集合,第二個引數 BiConsumer 將下一個元素收集到結果集合中,第三個引數 BiConsumer 用于將兩個結果集合合并起來,
生成亂數,保存到 LinkedList中
public static void main(String[] args) {
LinkedList<Integer> list = new Random(47)
.ints(10)
.collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
for (Integer integer : list) {
System.out.print(integer+ " ");
}
}
reduce() 組合
使用reduce() 組合所有流中的元素
-
reduce(BinaryOperator):使用 BinaryOperator 來組合所有流中的元素,因為流可能為空,其回傳值為 Optional, -
reduce(identity, BinaryOperator):功能同上,但是使用 identity 作為其組合的初始值,因此如果流為空,identity 就是結果, -
reduce(identity, BiFunction, BinaryOperator):更復雜的使用形式-
identity:組合函式的標識值,累加器的初始值, -
BiFunction:累加器,一個函式,用于將額外的元素合并到結果中 -
BinaryOperator:用于組合兩個值的關聯、不干擾、無狀態函式,必須與累加器函式兼容只有在并行流中才會執行
-
使用Stream.reduce()合并流的元素,并產生單個值
-
傳統方法使用for回圈求和
public static void main(String[] args) { int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int sum = 0; for(int i : numbers){ sum += i; } System.out.println(sum); // 55 } -
使用
reduce()求和public static void main(String[] args) { int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int sum = Arrays.stream(numbers).reduce(0, (a, b) -> a + b); System.out.println(sum); }累加器的初始值設定為0,
(a,b)-> a+b是累加器,結果保存到第一個引數中
count() 統計
統計流中的個數,回傳值是long型別
String[] arr = {"123", "456", "789"};
long cnt = Stream.of(arr).count();
System.out.println(cnt); // 3
max() 和 min() 最大數值
max()和min():數值流操作無需 Comparator,回傳一個Optional物件
陣列最大值、最小值
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println(Arrays.stream(arr).max()); // OptionalInt[10]
System.out.println(Arrays.stream(arr).max().getAsInt()); // 10
System.out.println(Arrays.stream(arr).min()); // OptionalInt[1]
System.out.println(Arrays.stream(arr).min().getAsInt()); // 1
}
max(Comparator) 和 min(Comparator)
max(Comparator):根據所傳入的 Comparator 所決定的“最大”元素,min(Comparator):根據所傳入的 Comparator 所決定的“最小”元素,- 回傳的都是
Optional物件
public static void main(String[] args) {
String[] str = {"java","c++","python"};
System.out.println(Arrays.stream(str).max(String::compareTo)); // Optional[python]
System.out.println(Arrays.stream(str).max(String::compareTo).get()); // python
}
sum() 求和
求流元素的和
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = Arrays.stream(arr).sum();
System.out.println(sum); // 55
}
average() 平均值
求流元素的平均值,回傳一個OptionalDouble物件
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
OptionalDouble average = Arrays.stream(arr).average();
System.out.println(average); // OptionalDouble[5.5]
System.out.println(average.getAsDouble()); // 5.5
}
匹配
allMatch(Predicate):如果流的每個元素提供給 Predicate 都回傳 true ,結果回傳為 true,在第一個 false 時,則停止執行計算,anyMatch(Predicate):如果流的任意一個元素提供給 Predicate 回傳 true ,結果回傳為 true,在第一個 true 時停止執行計算,noneMatch(Predicate):如果流的每個元素提供給 Predicate 都回傳 false 時,結果回傳為 true,在第一個 true 時停止執行計算,
說簡單,就是傳一個lambda運算式,然后回傳布林值
判斷陣列中是否全都為奇數
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 判斷所有元素是否都為奇數
boolean flag = Arrays.stream(arr).allMatch(i -> i % 2 == 1);
System.out.println(flag);
}
查找
findFirst():回傳第一個流元素的 Optional,如果流為慷訓傳 Optional.empty,findAny(:回傳含有任意流元素的 Optional,如果流為慷訓傳 Optional.empty,
public static void main(String[] args) {
String[] str = {"java","c++","python"};
System.out.println(Arrays.stream(str).findFirst()); // Optional[java]
System.out.println(Arrays.stream(str).findFirst().get()); // Java
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/167000.html
標籤:其他
上一篇:ConcurrentHashMap了解嗎?說說實作原理。
下一篇:Java連載143-三種系統注解
