在學習Stream以前必須有Lambda,的基礎,最好有泛型的基礎html
Stream是Java8的新特性,能夠進行對集合進行一些相似SQL的操做,例如篩選,排序,分組等,極大提升編碼效率java
而操做使用鏈式編程,只須要在源操做上.xxx()方法便可使用mysql
特色:sql
Stream基礎操做分類編程
中間操做 | 終止操做 |
---|---|
filter | allMatch |
limit | anyMatch |
skip | noneMatch |
distinct | findFirst |
map | findAny |
flatMap | count,max,min |
sorted | reduce |
collect |
操做Stream只須要3步 :數組
Stream的任何操做都是在stream對象上進行的,也就是說,任何Stream操做首先都須要獲取到Stream對象數據結構
能夠經過Collection系列集合提供的stream()串行流或parallelStream並行流來獲取流ide
public void test(){ List list=new ArrayList(); //獲取串行流 list.stream(); //獲取並行流 list.parallelStream(); }
對於串行和並行如今能夠簡單理解爲 串行上的操做是在一個線程中依次完成,而並行是在多個線程上同時執行,咱們如今僅須要使用串行流便可函數
經過Arrays中的靜態方法stream來獲取數組流工具
public void test(){ User [] users=new User[1]; Stream<User> stream = Arrays.stream(users); }
經過Stream的靜態方法of()
public void test(){ String[] strings=new String[10]; Stream<String> stream = Stream.of(strings); }
那麼如今已經獲取到Stream流了,咱們能夠進行相似SQL的操做
爲了方便演示,建立Student類
public class Student { //學生姓名 private String name; //學生年齡 private int age; //學生所在年級 private int grade; //性別,1男0女 private int sex; //省略構造,get,set,toString方法 }
在類初始化時添加一些數據
private static List<Student> studentList =new ArrayList<>(); //用於下面演示 private static Student student =new Student("小明",7,1,1); static{ studentList.add(student); studentList.add(student); studentList.add(new Student("小花",7,2,0)); studentList.add(new Student("小李",9,3,0)); studentList.add(new Student("小趙",8,3,1)); studentList.add(new Student("小王",7,3,0)); studentList.add(new Student("小亮",6,2,1)); studentList.add(new Student("小光",6,1,0)); }
首先來看filter 從流中排除元素
咱們來看一下filter方法參數
Stream<T> filter(Predicate<? super T> predicate); //看一下傳入參數,函數式接口,可使用Lambda @FunctionalInterface public interface Predicate<T> { boolean test(T t); }
那也就說明,咱們能夠本身定義判斷條件,結果返回一個boolean類型便可
public void test(){ Stream<Student> studentStream = studentList.stream() .filter((x) -> x.getAge() > 8); System.out.println("未執行終止操做前"); studentStream.forEach(System.out::println); } //結果,很明顯只有一個小李年齡大於8歲 未執行終止操做前 User{name='小李', age=9, grade='3', sex=0}
關於上面兩個來解釋一下,能夠理解爲x就是每次循環studentList中那個Student對象,使用lambda簡化了代碼而已,同時咱們證實了全部的中間操做都是惰性求值,只有執行到終止方法纔會真正的進行操做
若是不使用Lambda就是下面這樣,很是的麻煩
studentList.stream() .filter(new Predicate(){ @Override public boolean test(Object o) { Student student=(Student) o; return student.getAge()>8; } }) .forEach(new Consumer() { @Override public void accept(Object o) { System.out.println(o); } });
接下來看limit,相似於SQL中的limit,可是沒有起始值,只有須要的條數
limit具備短路功能,能夠提升效率, 假如list中有不少條數據,limit只須要找到設置的數量就中止查找,減小了資源的浪費
public void test3() { studentList.stream() .limit(2) .forEach(System.out::println); } //結果,只顯示兩條 User{name='小明', age=7, grade='1', sex=1} User{name='小明', age=7, grade='1', sex=1}
skip(n) 跳過n條數據,若是流中數據不足n條,那麼返回個空流, 這裏的空流指流裏沒有數據,而不是返回null
public void test3() { studentList.stream() .skip(6) .forEach(System.out::println); } //結果,只獲取到數據的最後兩條 User{name='小亮', age=6, grade='2', sex=1} User{name='小光', age=6, grade='1', sex=0} //====咱們來設置跳過條數大點,讓它返回一個空流看看 //由於返回的是個空流,forEach看不到效果,咱們使用另外一箇中間操做count,它返回的是流中數據的數量 public void test3() { long count = studentList.stream() .skip(100) .count(); System.out.println(count); } //結果 0
distinct 篩選,經過流所生成元素的hashcode和equals去除重複元素
public void test4() { studentList.stream() //排除重複 .distinct() .forEach(System.out::println); } //結果,只有一個小明,咱們在開始時添加了兩個同一對象的小明,distinct方法根據hashcode和equals去除了 //其餘Student數據....就不展現了
map方法,這個map不是咱們理解的那個相似於hashMap,它的做用是接收一個函數做爲參數,該函數會被應用到每一個元素上,並將其映射爲一個新的元素
什麼意思呢,來看下面的例子,例如數組中存放了學生的英文名字,咱們須要把名字所有轉換爲大寫
@Test public void test5() { String[] names = new String[]{"jame", "joker", "jack"}; Arrays.stream(names) .map((name)-> name.toUpperCase()) .forEach(System.out::println); } //結果 JAME JOKER JACK
能夠看到,全部的名字都轉換爲了大寫,那麼意味着數組中全部的字符串都使用了toUpperCase()轉大寫方法
咱們來看下面的這個例子
//這個方法將student對象轉換爲一個Stream<Student >流 public static Stream<Student> getStudentStream(Student student){ List<Student> studentList=new ArrayList<>(); studentList.add(student); return studentList.stream(); } //這個方法裏調用getStudentStream方法,將每個學生類都包裝爲Stream類型 public void test() { Stream<Stream<Student>> streamStream = studentList.stream() .map(MyStream::getStudentStream); streamStream.forEach((ss)->{ ss.forEach(System.out::println); }); } //結果 User{name='小明', age=7, grade='1', sex=1} User{name='小明', age=7, grade='1', sex=1} .....
能夠看到在從Stream中獲取Student對象時很麻煩,有簡單點的方法嗎?固然有,來看下面這個方法
flatMap 方法接收一個函數做爲參數,將流中每一個值轉換爲另外一個流,而後把全部流鏈接爲一個流
仍是上面那個例子,咱們將map替換爲flatMap方法,結果和上面一致就不展現了
public void test() { studentList.stream() .flatMap(MyStream::getStudentStream) .forEach(System.out::println); }
什麼原理呢?咱們上面的方法getStudentStream調用完成後每個學生對象都變爲一個流,可是它們仍是在最初的studentList的流中,相似套娃,流中還有一個流,而flatMap就是將最初的流中的全部流的數據都提取到最初的流中,造成一個流
好比揹包中有一個錢包,錢包裏面有錢,咱們打開揹包,而後打開錢包才能拿到錢,而如今flatMap把錢包中的錢都拿出來放入了揹包中,咱們只須要打開揹包便可拿到
sorted()若是不傳入參數,則根據天然排序
public void test() { studentList.stream() .sorted() .forEach(System.out::println); }
咱們啓動測試,出現了異常
java.lang.ClassCastException: com.jame.basics.stream.Student cannot be cast to java.lang.Comparable
爲何呢?原來sorted方法的天然排序會調用Comparable接口的compareTo方法,而咱們的Student根本沒有實現這個接口,咱們來實現一下,只是個簡單的判斷,判斷了年齡和年級
@Override public int compareTo(Object o) { Student student = (Student) o; if (student.getAge() > this.getAge()) return 1; if (student.getGrade() > this.getGrade()) return 1; return -1; }
那麼咱們再來試一下sorted方法,看一下結果
User{name='小李', age=9, grade='3', sex=0} User{name='小趙', age=8, grade='3', sex=1} User{name='小王', age=7, grade='3', sex=0} User{name='小花', age=7, grade='2', sex=0} User{name='小明', age=7, grade='1', sex=1} User{name='小明', age=7, grade='1', sex=1} User{name='小亮', age=6, grade='2', sex=1} User{name='小光', age=6, grade='1', sex=0}
能夠看到已經進行降序的排序了,若是想要升序排序只須要修改Student中的compareTo方法便可
那麼除了實現Comparabl接口來進行排序,還能有其餘辦法嗎? 還有一個辦法
咱們看到除了使用無參,還有個構造能夠傳入一個lambda,咱們能夠在lambda中定義排序規則
public void test() { studentList.stream() .sorted((s1,s2)->{ if(s1.getSex()>s2.getSex()) return 1; return -1; }) .forEach(System.out::println); } //結果 User{name='小光', age=6, grade='1', sex=0} User{name='小王', age=7, grade='3', sex=0} User{name='小李', age=9, grade='3', sex=0} User{name='小花', age=7, grade='2', sex=0} User{name='小亮', age=6, grade='2', sex=1} User{name='小趙', age=8, grade='3', sex=1} User{name='小明', age=7, grade='1', sex=1} User{name='小明', age=7, grade='1', sex=1}
咱們定義了根據性別排序,女生排在前面
全部的終止操做使用後會直接返回結果,後面不容許在進行任何操做
allMatch 查找全部元素是否符合條件
//判斷全部人的年齡是否都等於8 public void test10(){ boolean b = studentList.stream() .allMatch((x) -> x.getAge() == 8); } //結果 false
anyMatch 查找是否有一個符合條件
//判斷是否有一個學生年齡爲8 public void test10(){ boolean b = studentList.stream() .anyMatch((x) -> x.getAge() == 8); } //結果 true
noneMatch 查找是否有不符合條件的 全都不知足纔會返回true
//判斷是否有年齡不爲8的 public void test10(){ boolean b = studentList.stream() .noneMatch((x) -> x.getAge() == 8); System.out.println(b); } //false 雙重否認就是確定,也就是說學生中有年齡爲8歲的
findFirst 返回第一個元素
public void test10(){ Optional<Student> first = studentList.stream() .findFirst(); System.out.println(first.get()); } //結果 User{name='小明', age=7, grade='1', sex=1} //爲何返回的是一個Optional呢?咱們能夠簡單理解爲Optional就是對Student的一個包裝 //由於在Stream中,只有可能出現null的地方,都會返回一個包裝類
findAny 返回任意一個元素
public void test10(){ Optional<Student> first = studentList.stream() .findAny(); System.out.println(first.get()); } //這個方法通常和filter 方法一塊兒使用,單獨使用的話通常返回的都是第一個對象
count 返回Stream流中總數
public void test11() { long count = studentList.stream() .count(); System.out.println(count); } //結果 8
max 返回流中最大對象,咱們能夠自定義判斷條件
//這裏咱們使用學生類的年齡做爲判斷條件 public void test10() { Optional<Student> student = studentList.stream() .max((s1, s2) -> { if (s1.getAge() > s2.getAge()) return 1; return -1; }); System.out.println(student.get()); } //結果 User{name='小李', age=9, grade='3', sex=0}
min 返回流中最小對象 一樣,咱們也能夠自定義判斷的條件
//使用學生類的年級來判斷 public void test12() { Optional<Student> student = studentList.stream() .min((s1, s2) -> { if (s1.getGrade() > s2.getGrade()) return 1; return -1; }); System.out.println(student.get()); } //結果 User{name='小明', age=7, grade='1', sex=1}
reduce 能夠將流中元素反覆結合起來,獲得一個值,來看下面例子
public void test15() { List<Integer> list=new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); Optional<Integer> reduce = list.stream() .reduce((x, y) -> { System.out.println(x+"=="+y); return x+y; }); System.out.println(reduce.get()); } //結果 x:1==y:2 x:3==y:3 x:6==y:4 10
咱們能夠清楚的看到第一次進入這個方法時,流中的第一條數據賦值給了x,而y賦值了第2條數據,當執行完一遍後將結果賦值給了x,y的數據則依次迭代下去
還有一個注意的點: 返回的類型可能爲空,因此Stream使用了Optional來包裝
reduce還能夠設置起始值
public void test15() { List<Integer> list=new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); Integer reduce = list.stream() .reduce(5, (x, y) -> { System.out.println("x:" + x + "==y:" + y); return x + y; }); System.out.println(reduce); } //結果 x:5==y:1 x:6==y:2 x:8==y:3 x:11==y:4 15
當設置起始值後,第一次x的值爲設置的起始值,而y則爲流中第一條數據的值,之後的遍歷每次都會把return的值賦值給x,y的值則根據流中得數據迭代賦值
咱們能夠看到返回的類型已經不是Optional,緣由也能夠想到,由於賦值了起始值,因此不用怕傳入的流中沒有數據了
下面是一個小例子,用一個Student對象來保存整個學生集合中年齡最大的值,班級最大的值
public void test13() { Student s = new Student(); Optional<Student> reduce = studentList.stream() .reduce((x, y) -> { if (x.getAge() < y.getAge()) s.setAge(y.getAge()); if (x.getGrade() < y.getGrade()) s.setGrade(y.getGrade()); return s; }); System.out.println(reduce.get()); //結果 User{name='null', age=9, grade='3', sex=0}
若是不太理解能夠本身試試
若是咱們想經過Stream流來獲取List,Map,Set怎麼辦呢?
咱們能夠經過collect方法,將流轉換爲其餘形式,接收一個Collector接口的實現用於給Stream中元素作總彙的方法
例如咱們想把全部學生名字轉換爲一個Set集合
能夠看到須要一個Collector收集器接口來進行須要的收集操做,而JDK提供了一個Collectors的工具類來提供不一樣的收集操做
public void test16() { Set<String> nameSet = studentList.stream() //map接收一個函數做爲參數,該函數會被應用到每一個元素上,並將其映射爲一個新的元素 //而這個函數是getName,能夠理解爲每一個對象都調用getName方法,那麼返回的就全都是一個String類型的 .map(Student::getName) //toSet(),將結果收集爲一個Set集合 .collect(Collectors.toSet()); for (String s : nameSet) { System.out.println(s); } } //結果小光 小明 小李 ....
相似的操做還有toList,就再也不演示,也可使用toCollection來生成Collection接口類型下的其餘集合
public void test16() { studentList.stream() .map(Student::getName) .collect(Collectors.toCollection(HashSet::new)); }
也能夠經過收集器來獲取平均值
public void test16() { Double sAveragin = studentList.stream() //還有averagingInt averagingLong 就是結果類型不一樣,一個爲int 一個爲long .collect(Collectors.averagingDouble(Student::getAge)); System.out.println(sAveragin); } //結果 7.125
獲取總和
public void test16() { Double sAveragin = studentList.stream() //和上面獲取平均值同樣,還有不一樣的返回類型,相似summingInt等等 .collect(Collectors.summingDouble(Student::getAge)); System.out.println(sAveragin); } //結果 57.0
分組
相似於SQL的分組,那麼它的返回類型確定是一個map,該怎麼寫呢?來看下面的例子
//咱們根據年齡分組 public void test17() { Map<Integer, List<Student>> collect = studentList.stream() .collect(Collectors.groupingBy(Student::getAge)); Set<Integer> integers = collect.keySet(); for (Integer integer : integers) { System.out.println("key:"+integer+"===value:"+collect.get(integer)); } } //結果 key:6===value:[User{name='小亮', age=6, grade='2', sex=1}, User{name='小光', age=6, grade='1', sex=0}] key:7===value:[User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}, User{name='小花', age=7, grade='2', sex=0}, User{name='小王', age=7, grade='3', sex=0}] key:8===value:[User{name='小趙', age=8, grade='3', sex=1}] key:9===value:[User{name='小李', age=9, grade='3', sex=0}]
多級分組
好比咱們先按照年級分組,而後根據性別分組該怎麼辦呢?這時候就須要使用多級分組了
咱們能夠看到,這個Collectors.groupingBy()方法中還一個再傳入一個Collectors對象,一切都明白了,再寫一個收集器放入就能夠了,來看下面例子
public void test18() { Map<Integer, Map<Integer, List<Student>>> map = studentList.stream() .collect(Collectors.groupingBy( (x) -> x.getGrade(), Collectors.groupingBy((y) -> y.getSex()) )); Set<Integer> keySet = map.keySet(); for (Integer k : keySet) { System.out.println("年級爲:"+k); Map<Integer, List<Student>> map1 = map.get(k); Set<Integer> keySet1 = map1.keySet(); for (Integer k1 : keySet1) { System.out.println(map1.get(k1)); } } } } //結果 年級爲:1 [User{name='小光', age=6, grade='1', sex=0}] [User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}] 年級爲:2 [User{name='小花', age=7, grade='2', sex=0}] [User{name='小亮', age=6, grade='2', sex=1}] 年級爲:3 [User{name='小李', age=9, grade='3', sex=0}, User{name='小王', age=7, grade='3', sex=0}] [User{name='小趙', age=8, grade='3', sex=1}]
若是有多個條件,只須要在後面添加Collectors.groupingBy便可,不過條件多了取的時候就很是麻煩了
咱們也能夠根據數值本身設置key的值,須要注意的是key的類型必須和當前分組的類型一致
public void test18() { Map<Integer, Map<Integer, List<Student>>> map = studentList.stream() .collect(Collectors.groupingBy( (x) -> { if(x.getGrade()==1){ return 111; }else if(x.getGrade()==2){ return 222; }else { return 333; } }, Collectors.groupingBy((y) -> y.getSex()) )); Set<Integer> keySet = map.keySet(); for (Integer k : keySet) { System.out.println("最外層k的值:"+k); Map<Integer, List<Student>> map1 = map.get(k); Set<Integer> keySet1 = map1.keySet(); for (Integer k1 : keySet1) { System.out.println(map1.get(k1)); } } } //結果 最外層k的值:333 [User{name='小李', age=9, grade='3', sex=0}, User{name='小王', age=7, grade='3', sex=0}] [User{name='小趙', age=8, grade='3', sex=1}] 最外層k的值:222 [User{name='小花', age=7, grade='2', sex=0}] [User{name='小亮', age=6, grade='2', sex=1}] 最外層k的值:111 [User{name='小光', age=6, grade='1', sex=0}] [User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}]
partitioningBy 咱們還能夠經過某些判斷根據true和false來分組
public void test20() { Map<Boolean, List<Student>> map = studentList.stream() .collect(Collectors.partitioningBy((x) -> x.getGrade() >= 2)); Set<Boolean> booleans = map.keySet(); for (Boolean aBoolean : booleans) { System.out.println("年級是否大於等於2年級:"+aBoolean); List<Student> studentList = map.get(aBoolean); for (Student student : studentList) { System.out.println(student); } } } //結果 年級是否大於等於2年級:false User{name='小明', age=7, grade='1', sex=3} User{name='小明', age=7, grade='1', sex=3} User{name='小光', age=6, grade='1', sex=0} 年級是否大於等於2年級:true User{name='小花', age=7, grade='2', sex=0} User{name='小李', age=9, grade='3', sex=0} User{name='小趙', age=8, grade='3', sex=1} User{name='小王', age=7, grade='3', sex=0} User{name='小亮', age=6, grade='2', sex=1}
summarizingInt 能夠直接獲取全部元素的某一項數值得最大值,最小值,平均值,總和,咱們一塊兒來看下面的例子
public void test21() { IntSummaryStatistics collect = studentList.stream() .collect(Collectors.summarizingInt((x)->x.getAge())); System.out.println("學生年齡的最大值:"+collect.getMax()); System.out.println("學生年齡的最小值:"+collect.getMin()); System.out.println("學生年齡的平均值:"+collect.getAverage()); System.out.println("學生年齡的總和:"+collect.getSum()); } //結果 學生年齡的最大值:9 學生年齡的最小值:6 學生年齡的平均值:7.125 學生年齡的總和:57
還有相似的例如summarizingDouble,summarizingLong,只不過是返回的類型不一樣,這裏再也不展現
鏈接操做
能夠經過join方法將結果收集在一塊兒,下面例子
public void test22() { String collect = studentList.stream() .map(Student::getName) .collect(Collectors.joining()); System.out.println(collect); } //結果 小明小明小花小李小趙小王小亮小光
還能夠設置分割符,開頭符號,結尾符號
public void test22() { String collect = studentList.stream() .map(Student::getName) .collect(Collectors.joining(",","我是開頭符號","我是結尾符號")); System.out.println(collect); } //結果 我是開頭符號小明,小明,小花,小李,小趙,小王,小亮,小光我是結尾符號
到此,Stream的基本使用就完成了,感謝閱讀
本文僅我的理解,若是有不對的地方歡迎評論指出或私信,謝謝٩(๑>◡<๑)۶