什么是流式操作
Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數(shù)據(jù)。
Stream 使用一種類似用 SQL 語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。
Stream API可以極大提高Java程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡潔的代碼。
這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節(jié)點上進行處理, 比如篩選, 排序,聚合等。
元素流在管道中經(jīng)過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結果。
1.流式操作舉例
1.1創(chuàng)建實體類
public class Person {
private String name;
private Integer age;
private Integer score;
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 Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
public Person() {
}
public Person(String name, Integer age, Integer score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
1.2 傳統(tǒng)的對象初始化方式
public class Program {
public static void main(String[] args) {
//使用構造器設置對象信息
// Person xiaomign = new Person("小明", 28, 90);
//使用getter、setter方式設置對象信息
Person xiaoming = new Person();
xiaoming.setName("小明");
xiaoming.setAge(18);
xiaoming.setScore(90);
}
}
1.3 使用流式操作初始化對象
1.3.1 修改實體類
public class Person {
private String name;
private Integer age;
private Integer score;
public String getName() {
return name;
}
public Person setName(String name) {
this.name = name;
return this;
}
public Integer getAge() {
return age;
}
public Person setAge(Integer age) {
this.age = age;
return this;
}
public Integer getScore() {
return score;
}
public Person setScore(Integer score) {
this.score = score;
return this;
}
public Person() {
}
public Person(String name, Integer age, Integer score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
1.3.2 使用流式操作
//流式操作
xiaoming.setName("小明").setAge(20).setScore(100);
2.集合的流式操作
集合的流式操作是Java8的一個新特性,流式操作不是一個數(shù)據(jù)結構,不負責任何的數(shù)據(jù)存儲,它更像是一個迭代器,可以有序的獲取數(shù)據(jù)源中的每一個數(shù)據(jù),并且可以對這些數(shù)據(jù)進行一些操作。流式操作的每一個方法的返回值都是這個流的本身。
2.1 流式操作的三個步驟
2.1.1 獲取數(shù)據(jù)源:集合、數(shù)組
-
設置數(shù)據(jù)源
public class Data { /** * 數(shù)據(jù)源 */ public static ArrayList<Person> getData() { ArrayList<Person> list = new ArrayList<Person>(); list.add(new Person("小明", 18, 100)); list.add(new Person("小麗", 19, 70)); list.add(new Person("小王", 22, 85)); list.add(new Person("小張", 20, 90)); list.add(new Person("小黑", 21, 95)); return list; } } -
獲取數(shù)據(jù)源的方式
public class Program { public static void main(String[] args) { // 獲取數(shù)據(jù)源方式1 Stream stream = Data.getData().stream(); // 獲取數(shù)據(jù)源方式2 Stream.of(Data.getData()); // 獲取數(shù)據(jù)源方式3 //數(shù)據(jù)源為數(shù)組 } }
2.1.2 對數(shù)據(jù)進行處理的過程:過濾、排序、映射等(中間操作)
中間操作1:filter
-
使用filter自定義條件過濾數(shù)據(jù)
// 中間操作1: filter // filter是一個過濾器,可以自定義一個過濾條件,將流中滿足條件的元素保留 // 查找集合中成績小于80的學生 List<Person> list = Data.getData().stream() .filter(ele -> ele.getScore() < 80) .collect(Collectors.toList()); System.out.println(list);
中間操作2:distinct
-
使用distinct實現(xiàn)去重操作
在數(shù)據(jù)源中添加重復的數(shù)據(jù)
list.add(new Person("小黑", 21, 95)); //此時list中有兩個小黑在實體類中重寫hashCode()和equals()方法
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(name, person.name) && Objects.equals(age, person.age) && Objects.equals(score, person.score); } @Override public int hashCode() { return Objects.hash(name, age, score); }去重規(guī)則:
先判斷對象的hashCode()
如果hashCode()相同再判斷equals()
// 中間操作2: distinct // distinct: 取出集合中不同的元素 // 去重規(guī)則: // 1.先判斷對象的hashCode() // 2.如果hashCode()相同再判斷equals() Data.getData().stream().distinct().forEach(System.out::println);
注意:如果小黑的數(shù)據(jù)相同卻要保存兩份,可以在hashCode()方法中返回一個隨機數(shù),隨機數(shù)很小概率會相同,為了確保穩(wěn)定性,可以將equals()方法改為返回false,這樣可以保留兩個信息相同的小黑。
中間操作3:sorted
-
使用
sorted()方法以成績進行升序排序要求實體類實現(xiàn)Comparable接口并重寫方法
// 中間操作3: sorted // sorted: 對返回的元素進行排序 // sorted(): 要求實體類實現(xiàn)Comparable接口并重寫方法 Data.getData().stream().sorted().forEach(System.out::println);
中間操作4:limit
-
在數(shù)據(jù)源中取前三個數(shù)據(jù)
// 中間操作4: limit // limit: 限制,只取流中前指定位的數(shù)據(jù) // 在數(shù)據(jù)源中取前三個數(shù)據(jù) Data.getData().stream().limit(3).forEach(System.out::println);
中間操作5:skip
-
跳過前三個元素,取后面剩下的元素
// 中間操作5: skip // skip: 跳過 // 跳過前三個元素,取后面剩下的元素 Data.getData().stream().skip(3).forEach(System.out::println);
中間操作6:map
元素映射,用指定的元素替換掉流中的元素
-
使用map將對象替換為對象的名字
// 中間操作6: map // map: 元素映射,用指定的元素替換掉流中的元素 // 將流中的Person對象替換位他們的姓名 Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println);
2.1.3 對流中數(shù)據(jù)的整合:轉成集合、數(shù)量(最終操作)
最終操作1:collect
-
轉換為List
public class Program { public static void main(String[] args) { // 獲取數(shù)據(jù)源方式1 Stream<Person> stream = Data.getData().stream(); // 最終操作1: collect,配合Collectors使用 // 將集合中的元素轉換成List List<Person> list = stream.collect(Collectors.toList()); System.out.println(list); } }運行結果
-
轉換為set
// 將集合中的元素轉換為Set Set<Person> set = stream.collect(Collectors.toSet()); System.out.println(set);運行結果
-
轉換為map
// 轉換為Map(name為鍵,score為值) // 方式1 // Map<String, Integer> map = stream.collect(Collectors.toMap( // ele -> ele.getName(), // ele -> ele.getScore() // )); // 方式2 Map<String, Integer> map = stream.collect(Collectors.toMap( Person::getName, Person::getScore ));運行結果
最終操作2:reduce
reduce的思想
比如在計算一個數(shù)組中的元素的和時,首先會計算前兩個數(shù)的和,然后拿著前兩個數(shù)的和與第三個數(shù)求和,計算出結果后將三個數(shù)的和與第四個數(shù)相加,以此類推。
-
計算數(shù)組中數(shù)據(jù)的和
// 最終操作2: reduce(將數(shù)據(jù)匯總在一起) Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Optional<Integer> res = stream1.reduce((n1, n2) -> n1 + n2); // 獲取到最終的返回值 System.out.println(res.get());
-
使用reduce計算Person對象中成績的和
// 計算Person中Score的和 Optional<Person> res = stream.reduce( (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore()) ); System.out.println(res.get().getScore());
缺點:上面的寫法每次都會產(chǎn)生一個臨時的對象,產(chǎn)生了不必要的性能損耗
-
使用reduce計算Person對象中成績的和(優(yōu)化)
// 計算Person中Score的和(使用臨時變量,減少性能開銷) Person temp = new Person(); Optional<Person> res = stream.reduce( (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore()) ); System.out.println(res.get().getScore());
最終操作3:max和min
-
使用max找出Person中成績最高的人
// 最終操作3: max和min // 需求1: 找到集合中成績最高的人的信息 Person max = stream.max( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get(); System.out.println(max);
-
使用min找出Person中成績最低的人
// 需求2: 找到集合中成績最低的人的信息 Person min = stream.min( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get(); System.out.println(min);
最終操作4:matching
-
使用anyMatch查看集合中是否有成績高于80的人
// 判斷集合中是否包含成績大于80的學員 boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80); System.out.println(res1);
-
使用allMatch查看集合中的成績是否全部高于60
//查看集合中的人的成績是否全部高于60 boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60); System.out.println(res2);
-
使用noneMatch查看集合中的人的分數(shù)是否不包含80以下的
boolean res3 = stream.noneMatch((ele) -> ele.getScore() < 80); System.out.println(res3);
最終操作5:count
-
使用count計算元數(shù)據(jù)中有多少條數(shù)據(jù)
// 最終操作5: 求元數(shù)據(jù)中有多少個元素 long count = stream.count(); System.out.println(count);
最終操作6:forEach
-
使用forEach遍歷集合中的元素
// 最終操作6: forEach // stream.forEach(ele -> System.out.println(ele)); stream.forEach(System.out::println);
最終操作7:findFirst和findAny
- FindFirst: 獲取流中的第一個元素
FindAny: 獲取流中任意一個元素(并不是隨機獲取元素)
對于串行流,結果等同于findFirst
findAny用于并行流中可能會與findFirst一樣,也可能不一樣
// FindFirst: 獲取流中的第一個元素
// FindAny: 獲取流中任意一個元素(并不是隨機獲取元素)
// 對于串行流,結果等同于findFirst
// findAny用于并行流中可能會與findFirst一樣,也可能不一樣
System.out.println(Data.getData().parallelStream().findFirst());
System.out.println(Data.getData().stream().findFirst());
System.out.println(Data.getData().parallelStream().findAny());
System.out.println(Data.getData().stream().findAny());
最終操作的注意事項
-
為什么會被稱為最終操作?
Person max = stream.max( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get(); Person min = stream.min( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get();
報錯信息表示流正在被處理或者已經(jīng)被關閉了,如果已經(jīng)被關閉了再次調(diào)用當然會報錯,這也是為什么叫最終操作的原因。
3.并行流
3.1 獲取并行流的方式
// 并行流
// 獲取并行流的兩種方式
Data.getData().stream().parallel();
Data.getData().parallelStream();
3.2 并行流與串行流對比
// 串行流: 19920ms
// 并行流: 12204ms
long startTime = System.currentTimeMillis();
//LongStream.rangeClosed(0L, 50000000000L)
// .reduce(Long::sum);
LongStream.rangeClosed(0L, 50000000000L)
.parallel()
.reduce(Long::sum);
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
3.3 flatMap
String[] array = {"hello", "world"};
// 需要獲取所有字符 List -> h, e, l, l, o, w, o, r, l, d
// Arrays.stream(array)
// .map(ele -> ele.split(""))
// .forEach(ele -> System.out.println(ele.length));
System.out.println(Arrays.stream(array)
.map(ele -> ele.split(""))
.flatMap(Arrays::stream)
.collect(Collectors.toList()));
4.Collectors
Collectors是一個工具類,提供著若干個方法,返回一個Collector接口的實現(xiàn)類對象
4.1 maxBy
? 通過指定的規(guī)則獲取流中最大的元素
System.out.println(Data.getData().stream()
.collect(Collectors.maxBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
4.2 minBy
? 通過指定的規(guī)則獲取流中最小的元素
System.out.println(Data.getData().stream()
.collect(Collectors.minBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
4.3 joining
合并,將流中的元素,以字符串的形式拼接起來
// 把Person中的姓名拼成一個字符串
String res1 = Data.getData().stream()
.map(Person::getName)
.collect(Collectors.joining());
System.out.println(res1);
String res2 = Data.getData().stream()
.map(Person::getName)
.collect(Collectors.joining("-"));
System.out.println(res2);
String res3 = Data.getData().stream()
.map(Person::getName)
.collect(Collectors.joining("-", "{", "}"));
System.out.println(res3);
4.4 summingInt
計算int類型的和,將流中的元素映射為int類型的元素進行求和
-
將Person對象的成績進行求和
// 將Person對象的成績進行求和 System.out.println(Data.getData().stream() .collect(Collectors.summingInt(ele -> ele.getScore())));
4.5 averagingInt
計算int類型的平均值
-
計算不及格學生的平均成績
System.out.println(Data.getData().stream() .filter(ele -> ele.getScore() < 60) .collect(Collectors.averagingInt(Person::getScore)));
4.6 summarizingInt
將流中的元素映射成int類型的元素,獲取這些數(shù)據(jù)的描述信息
System.out.println(Data.getData().stream()
.collect(Collectors.summarizingInt(ele -> ele.getScore())));