Java8新特性 - Stream API
Stream是Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作。使用Stream API對集合進行操作,就類似與使用SQL執行的資料庫操作。也可以使用Stream API來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用的處理資料的方式。
什麼是Stream
是資料渠道,用於操作資料來源(集合、陣列等)所生成的元素序列
集合講的是資料,流講的是計算
PS:
- Stream自己不會儲存元素
- Stream不會改變源物件,相反,他們會返回一個持有結果的新Stream
- Stream操作是延遲執行的,這意味著他們會等到需要結果的時候才執行
下面的圖可以比較直觀的反映這一過程:
- 建立Stream
一個數據源(陣列、集合等),獲取一個流 - 中間操作
一箇中間操作鏈,對資料來源的資料進行處理 - 終止操作(終端操作)
一個終止操作,執行中間操作鏈,併產生結果
建立Stream
通過Collection系列集合提供的stream()或parallelStream()
Java8中的Collection介面被擴充套件,提供了兩個獲取流的方法:
- default Stream
- default Stream
示例程式碼:
List<Integer> list = new ArrayList<>(); Stream<Integer> stream1 = list.stream(); Stream<Integer> stream2 = list.parallelStream();
由陣列建立流
Java8的Arrays的靜態方法stream()可以獲取資料流
- static
示例程式碼:
Integer[] integers = new Integer[10];
Stream<Integer> stream = Arrays.stream(integers);
由值建立流
通過Stream類中的靜態方法of(),通過顯示值建立一個流,可以接收任意數量的引數
- public static
示例程式碼:
Stream<Integer> stream = Stream.of(1, 2, 3);
建立無限流
使用靜態方法Stream.iterate()和Stream.generate(),建立無限流
- 迭代:public static
- 生成:public static
示例程式碼:
// 迭代
Stream stream1 = Stream.iterate(0, (x) -> x + 2);
// 生成
Stream stream2 = Stream.generate(() -> Math.random());
中間操作
多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱之為“惰性求值”。
篩選與切片
- filter:結合搜lambda,從流中排除元素
- limit:截斷流,使其元素不超過給定數量
- skip(n):跳過元素,返回一個刪除了前n個元素的流;若流中元素不足n個,則返回一個空流;與limit(n)互補
- distinct:篩選,通過流所生成的元素的hashCode()和equals()去除重複元素
示例程式碼:
public class TestStreamApi {
private static List<Demo> demoList = Arrays.asList(
new Demo(1, "哈哈哈"),
new Demo(2, "嘿嘿嘿嘿"),
new Demo(3, "呵呵呵"),
new Demo(4, "恩恩恩恩"),
new Demo(5, "哼哼哼"),
new Demo(6, "嘖嘖嘖"),
new Demo(5, "哼哼哼"),
new Demo(8, "哼")
);
public static void main(String[] args) {
// 中間操作不會執行任何操作
Stream<Demo> demoStream = demoList.stream()
.filter((x) -> x.getRemark().length() == 3)
.limit(4)
.skip(1)
.distinct();
// 終止操作一次性執行全部內容
// 內部迭代:迭代操作由Stream API完成
demoStream.forEach(System.out::println);
}
}
執行結果:
3-呵呵呵
5-哼哼哼
6-嘖嘖嘖
注意:distinct篩選通過流所生成的元素的hashCode()和equals()去除重複元素,所以需要重寫Demo的hashCode()和equals()方法。
對映
- map:接收Lambda,將元素轉換成其它形式或提取資訊;接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素
- flatMap:接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有的流連線成一個流
示例程式碼:
public class TestStreamApi {
private static List<Demo> demoList = Arrays.asList(
new Demo(1, "哈哈哈"),
new Demo(2, "嘿嘿嘿嘿")
);
public static void main(String[] args) {
demoList.stream()
.map(Demo::getRemark)
.flatMap(TestStreamApi :: filterCharacter)
.forEach(System.out::println);
}
public static Stream<Character> filterCharacter(String str) {
List<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
}
執行結果:
哈
哈
哈
嘿
嘿
嘿
嘿
排序
- sorted():自然排序
- sorted(Comparator c):定製排序
示例程式碼:
public class TestStreamApi {
private static List<Demo> demoList = Arrays.asList(
new Demo(5, "哈哈哈"),
new Demo(2, "嘿嘿嘿嘿"),
new Demo(3, "呵呵呵"),
new Demo(2, "哼哼哼"),
new Demo(5, "嘖嘖嘖")
);
public static void main(String[] args) {
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
list.stream()
.sorted()
.forEach(System.out::println);
System.out.println("----------");
demoList.stream()
.sorted((x, y) -> {
if (x.getNum().equals(y.getNum())) {
return x.getRemark().compareTo(y.getRemark());
} else {
return x.getNum().compareTo(y.getNum());
}
})
.forEach(System.out::println);
}
}
執行結果:
aaa
bbb
ccc
----------
2-哼哼哼
2-嘿嘿嘿嘿
3-呵呵呵
5-哈哈哈
5-嘖嘖嘖
終止操作
查詢與匹配
- allMatch:檢查是否匹配所有元素
- anyMatch:檢查是否匹配所有元素
- noneMatch:檢查是否沒有匹配所有元素
- findFirst:返回第一個元素
- findAny:返回當前流中的任意元素
- count:返回流中元素的總個數
- max:返回流中的最大值
- min:返回流中的最小值
示例程式碼:
public class TestStreamApi2 {
private static List<Demo> demoList = Arrays.asList(
new Demo("張三", 18, 6666.66, Demo.Status.BUSY),
new Demo("李四", 38, 3333.33, Demo.Status.FREE),
new Demo("王五", 28, 5555.55, Demo.Status.FREE),
new Demo("趙六", 48, 7777.77, Demo.Status.BUSY),
new Demo("王二麻子", 58, 8888.88, Demo.Status.VOCATION)
);
public static void main(String[] args) {
// 是不是所有的物件都處於BUSY狀態
System.out.println(demoList.stream()
.allMatch((d) -> d.getStatus().equals(Demo.Status.BUSY)));
// 是否有物件處於BUSY狀態
System.out.println(demoList.stream()
.anyMatch((d) -> d.getStatus().equals(Demo.Status.BUSY)));
// 是否沒有物件處於BUSY狀態
System.out.println(demoList.stream()
.noneMatch((d) -> d.getStatus().equals(Demo.Status.BUSY)));
// 獲取工資最高的
Optional<Demo> optionalDemo1 = demoList.stream()
.sorted((x, y) -> -Double.compare(x.getSalary(), y.getSalary()))
.findFirst();
System.out.println(optionalDemo1.get());
// 獲取隨機一個空閒的
Optional<Demo> optionalDemo2 = demoList.stream()
.filter((e) -> e.getStatus().equals(Demo.Status.FREE))
.findAny();
System.out.println(optionalDemo2.get());
// 總數
System.out.println(demoList.stream().count());
// 工資最高的
Optional<Demo> optionalDemo3 = demoList.stream()
.max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
System.out.println(optionalDemo3.get());
// 最小的工資
Optional<Double> optionalDemo4 = demoList.stream()
.map(Demo::getSalary)
.max(Double::compare);
System.out.println(optionalDemo4.get());
}
}
class Demo{
// 姓名
String name;
// 年齡
Integer age;
// 工資
Double salary;
// 狀態
Status status;
public Demo() {}
public Demo(String name, Integer age, Double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Demo demo = (Demo) o;
return name.equals(demo.name) &&
age.equals(demo.age) &&
salary.equals(demo.salary) &&
status == demo.status;
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary, status);
}
@Override
public String toString() {
return "Demo{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", status=" + status +
'}';
}
public enum Status{
FREE,
BUSY,
VOCATION
}
}
執行結果:
false
true
false
Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}
Demo{name='李四', age=38, salary=3333.33, status=FREE}
5
Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}
8888.88
歸約
- reduce(T identify, BinaryOperator) / reduce(BinaryOperator):可以將流中元素反覆結合起來,得到一個值
示例程式碼:
public class TestStreamApi3 {
private static List<Demo> demoList = Arrays.asList(
new Demo("張三", 18, 6666.66, Demo.Status.BUSY),
new Demo("李四", 38, 3333.33, Demo.Status.FREE),
new Demo("王五", 28, 5555.55, Demo.Status.FREE),
new Demo("趙六", 48, 7777.77, Demo.Status.BUSY),
new Demo("王二麻子", 58, 8888.88, Demo.Status.VOCATION)
);
public static void main(String[] args) {
Optional<Double> optional = demoList.stream()
.map(Demo::getSalary)
.reduce(Double::sum);
System.out.println(optional.get());
}
}
執行結果:
32222.190000000002
收集
- collect:將流轉換為其他形式。接收一個Collection介面的實現,用於給Stream中元素做彙總的方法
Collectors介面中方法的實現決定了如何對流執行收集操作(如蒐集到List、Set、Map)。
- toList:把流中元素收集到List
- toSet:把流中元素收集到Set
- toCollection:把流中元素收集到建立的集合
- counting:計算流中元素的個數
- summingInt:對流中元素的整數屬性求和
- averagingInt:計算流中元素Integer屬性的平均值
- summarizingInt:收集流中Integer屬性的統計值
- jioning:連線流中的每個字串
- maxBy:根據比較器選擇最大值
- minBy:根據比較器選擇最小值
- reducing:從一個作為累加器的初始值開始,利用BinaryOperator與流中元素逐個結合,從而規約成單個值
- collectingAndThen:包裹另一個收集器,對其結果轉換函式
- groupingBy:根據某個屬性值對流分組,屬性為K,結果為V
- partitioningBy:根據true、false進行分割槽
示例程式碼:
public class TestStreamApi4 {
private static List<Demo> demoList = Arrays.asList(
new Demo("張三", 18, 6666.66, Demo.Status.BUSY),
new Demo("李四", 38, 3333.33, Demo.Status.FREE),
new Demo("王五", 28, 5555.55, Demo.Status.FREE),
new Demo("趙六", 48, 7777.77, Demo.Status.BUSY),
new Demo("王二麻子", 58, 8888.88, Demo.Status.VOCATION)
);
public static void main(String[] args) {
HashSet<String> set = demoList.stream()
.map(Demo::getName)
.collect(Collectors.toCollection(HashSet::new));
set.forEach(System.out::println);
// 總數
System.out.println(demoList.stream()
.collect(Collectors.counting()));
// 工資平均值
System.out.println(demoList.stream()
.collect(Collectors.averagingDouble(Demo::getSalary)));
}
}
執行結果:
李四
張三
王二麻子
王五
趙六
5
6444.