Java Stream使用
這段時間在學數據庫和Java,發現Java的Stream實際上和數據庫的查詢操作非常類似。這里簡單介紹Stream的用法,并和Sql Server中的操作聯系起來。
此文為初學Stream所寫,以后對Stream有更深的理解后會重寫
當我們使用一個流的時候,通常包括三個基本步驟:
獲取一個數據源(source)
數據轉換
執行操作獲取想要的結果
每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以像鏈條一樣排列,變成一個管道,如下圖所示。
一、創建 stream
有多種方式生成 Stream Source:
從 Collection 和數組
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array) or Stream.of()
從 BufferedReader
java.io.BufferedReader.lines()
靜態工廠
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
自己構建
java.util.Spliterator
其它
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
創建Stream示例
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List list = Arrays.asList(strArray);
stream = list.stream();
二、stream 操作
stream的操作分為兩大類,一類為中間操作,一類為終端操作。
中間操作:返回值仍然為一個流,不會消耗流
終端操作:返回最終結果;終端操作會消耗掉流,使之不再可用
1.stream.filter()
stream.filter() 是一個中間操作
stream.filter()用于對stream進行某種篩選,stream.filter() 相當于Sql server 中,from ... where ...
在filter()中應當給出篩選條件,準確的說,應該實現Predicate接口,這個接口將被應用于stream中的每一個元素,判斷其是否應該被包含在結果stream中。
這個接口只有一個抽象方法待用戶實現
抽象方法應該返回一個布爾值,當布爾值為真時,stream.filter()將這個元素包含在結果stream中
stream.filter()使用示例:創建Integer流,然后篩選出偶數
ArrayList arrlist = new ArrayList();
Stream st = arrlist.stream();
Stream st2 = st.filter(new Predicate() {
@Override
public boolean test(Integer arg0) {
return arg0 % 2 == 0;
}
});
還可以用lambda表達式來實現
ArrayList arrlist = new ArrayList();
Stream st = arrlist.stream();
Stream st2 = st.filter((o1)->(o1 % 2 == 0));
關于Predicate接口,它還有.and(),.or(),.negate(),.isEqual()四個默認方法,這里不多介紹。但這些方法也十分常用,對于稍微復雜一點的邏輯就需要使用。
使用Precicate接口需要導入
import java.util.function.Predicate;
2.stream.map()
stream.map()是一個中間操作
stream.map()用于對stream進行某種映射,stream.map() 相當于Sql server 中,select
雖然這么說不太恰當,因為Sql sever 的select實際上時 SQL語言中 \(\sigma , \prod\)的加和,而stream.map() 應該是\(\prod\).
在stream.map()中應該指定轉換條件,準確的說,應該實現一個Function()接口,這個接口將被用于stream的每一個元素,將元素按照一定的映射關系映射成新的元素。
Function接口的參數意義
使用Function接口需要導入
import java.util.function.Function;
這個接口只有一個抽象方法待用戶實現
抽象方法apply() 接受一個T類型的參數,返回一個R類型的結果
stream.map()使用示例:創建Integer流,然后映射到其原值的兩倍
ArrayList arrlist = new ArrayList();
for(int i = 1; i <= 5; i++) {
arrlist.add(i);
}
Stream st = arrlist.stream();
Stream st2 = st.map(new Function() {
@Override
public Integer apply(Integer arg0) {
return arg0 * 2;
}
});
ArrayList ans = new ArrayList();
ans = (ArrayList) st2.collect(Collectors.toList());
for(Integer i : ans) {
System.out.print(i + " ");
}
還可以用lambda表達式來實現
// 初學可以先不這么寫
ArrayList ans = (ArrayList)arrlist.stream()
.map((o1)->(2*o1))
.collect(Collectors.toList());
stream.map() 的幾種其他形式
IntStream mapToInt(ToIntFunction super T> mapper);
LongStream mapToLong(ToLongFunction super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction super T> mapper);
這三者實際上是對\(Function\) 中 R的固定封裝
3.stream.flatMap()
stream.flatMap()是一個中間操作
stream.flatMap()和stream.map()都是進行映射的方法,區別在于,flatMap()處理的元素類型仍是流,flagMap用于將若干個流先拆分成若干個單個元素,再整合成一個流,即流的合并。
簡單來說,flatMap()將集合的集合降維成單個元素的集合
實例: 將數組\([[1,2,3],[4,5,6],[7,8],[9]]\)轉化為[1,2,3,4,5,6,7,8,9]
ArrayList> list_2 = new ArrayList<>();
list_2.add(new ArrayList<>(Arrays.asList(1,2,3)));
list_2.add(new ArrayList<>(Arrays.asList(4,5,6)));
list_2.add(new ArrayList<>(Arrays.asList(7,8)));
list_2.add(new ArrayList<>(Arrays.asList(9)));
ArrayList list_1 = (ArrayList) list_2.stream()
// list_2.stream() 為 "[1,2,3]" "[4,5,6]" "[7,8]" "[9]" 每個""表示流的不同元素
.flatMap((o1)->(o1).stream())
// 以 o1 = "[1,2,3]"為例,(o1)->(o1).stream() 轉化為"1","2","3"
.collect(Collectors.toList());
for(Integer i : list_1) {
System.out.print(i+" ");
}
stream.flagMap()的幾種其他形式
IntStream flatMapToInt(Function super T, ? extends IntStream> mapper);
LongStream flatMapToLong(Function super T, ? extends LongStream> mapper);
DoubleStream flatMapToDouble(Function super T, ? extends DoubleStream> mapper);
這三者實際上是對\(Function\) 中 R的固定封裝
4.stream.allMatch() ,stream.anyMatch() 和 stream.noneMatch()
stream.allMatch() 和 stream.anyMatch()均為終端操作
傳入一個Predicate函數式接口,用于指定條件
5.stream.collect()
stream.collect()為終端操作
Stream的核心在于collect,即對數據的收集。
用法一:將流轉化為Collection或Map
Collectors.toCollection() 將數據轉換成Collection,只要是Collection的實現都可以,例如ArrayList,HashSet,該方法能夠接受一個Collection對象
示例:
//List
Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toCollection(ArrayList::new));
//Set
Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toCollection(HashSet::new));
// Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toList());
// Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toSet());
// Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toMap(key,value));
用法二:字符串聚合規約
Collectors.joining(),拼接,有三個重載方法,底層實現是StringBuilder,通過append方法拼接到一起,并且可以自定義分隔符(這個感覺還是很有用的,很多時候需要把一個list轉成一個String,指定分隔符就可以實現了,非常方便)、前綴、后綴。
Student studentA = new Student("20190001", "小明");
Student studentB = new Student("20190002", "小紅");
Student studentC = new Student("20190003", "小丁");
//使用分隔符:201900012019000220190003
Stream.of(studentA, studentB, studentC)
.map(Student::getId)
.collect(Collectors.joining());
//使用^_^ 作為分隔符
//20190001^_^20190002^_^20190003
Stream.of(studentA, studentB, studentC)
.map(Student::getId)
.collect(Collectors.joining("^_^"));
//使用^_^ 作為分隔符
//[]作為前后綴
//[20190001^_^20190002^_^20190003]
Stream.of(studentA, studentB, studentC)
.map(Student::getId)
.collect(Collectors.joining("^_^", "[", "]"));
用法三:統計個數
Collectors.counting() 統計元素個數,這個和Stream.count() 作用都是一樣的,返回的類型一個是包裝Long,另一個是基本long,但是他們的使用場景還是有區別的,這個后面再提。
// Long 8
Stream.of(1,0,-10,9,8,100,200,-80)
.collect(Collectors.counting());
//如果僅僅只是為了統計,那就沒必要使用Collectors了,那樣更消耗資源
// long 8
Stream.of(1,0,-10,9,8,100,200,-80)
.count();
用法四:集合分組
Collectos.groupingBy()實現集合分組,返回值為一個Map
假如現在有一個實體Student
public class Student {
private String name;
private int score;
private int age;
public Student(String name,int score,int age){
this.name = name;
this.score = score;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
現在對其按照Name分組
Map> StrListStrMap = students.stream()
.collect(Collectors.groupingBy(Student::getName));
6.stream.forEach() 和
stream.forEach() 終端操作
stream.forEach()遍歷流中的每一個元素,不一定依靠流的順序,而stream.forEachOrdered()按照流的順序遍歷。
Stream.of(1,2,3,4,5,6).forEach(System.out::println);
7.stream.max() , stream.min() , stream.count()
三個終端操作
stream.max()返回流中的最大值
stream.min()返回流中的最小值
未傳入Comparator則填null,默認用Comparable的compareTo函數比較。
stream.count()返回流中元素個數
8.stream.findAny()
返回流中任意一個元素,如果流為空,返回一個空的Optional.
List list = Arrays.asList(1, 2, 3, 4, 5, 6);
Optional any = list.stream().findAny();