Java8中除了引入了好用的Lambda表達式、Date API以外,另外還有一大亮點就是Stream API了,也是最值得全部Java開發人員學習的一個知識點,由於它的功能很是的強大,尤爲是和前面學習的Lambda表達式、函數式接口、方法引用共同使用的時候。html
Stream流是數據渠道,用於操做數據源所生成的元素序列,即操做集合、數組等等。其中集合和stream的區別是:集合講的是數據,而stream講的是計算。Stream的API所有都位於java.util.stream這樣一個包下,它可以幫助開發人員從更高的抽象層次上對集合進行一系列操做,就相似於使用SQL執行數據庫查詢。而藉助java.util.stream包,咱們能夠簡明的聲明性的表達集合,數組和其餘數據源上可能的並行處理。實現從外部迭代到內部迭代的改變。它含有高效的聚合操做、大批量的數據處理,同時也內置了許多運算方式,包括篩選、排序、聚合等等。簡單來講,用Stream來操做集合——減小了代碼量,加強了可讀性,提升運行效率。java
而後這裏要提一下:Stream和Java IO中的stream是徹底不一樣概念的兩個東西。本文要講解的stream是可以對集合對象進行各類串行或併發彙集操做,而Java IO流用來處理設備之間的數據傳輸,它們二者大相徑庭。數據庫
Stream的主要特色有:
一、stream自己並不存儲數據,數據是存儲在對應的collection(集合)裏,或者在須要的時候才生成的。
二、stream不會修改數據源,老是返回新的stream。
三、stream的操做是延遲(lazy)執行的:僅當最終的結果須要的時候纔會執行,即執行到終止操做爲止。數組
首先咱們先使用之前的方法來對集合進行一些操做,代碼示例以下:網絡
public class StreamTest { public static void main(String[] args) { //建立集合 ArrayList<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); list.add("four"); list.add("five"); list.add("six"); //遍歷數據,只遍歷4個 for (int i = 0; i < list.size()-1; i++) { //過濾掉字符串——two if (!(list.get(i).contains("two"))) { System.out.println(list.get(i)); } } } }
我相信像上面這樣的代碼估計每一個java開發人員分分鐘均可實現。但若是要求再複雜一點,代碼量就會大量增長,因此再經過建立一個存儲數據的集合,而後對集合進行遍歷,再經過特定的條件進行篩選而且將結果輸出。這樣的代碼中或多或少會有缺點的:
一、代碼量一多,維護確定更加困難。可讀性也會變差。併發
二、難以擴展,要修改成並行處理估計會花費很大的精力。app
而後下面展現了用Stream來實現相同的功能,很是的簡潔,若是須要添加其餘的方法也很是的方便。注:Stream中的一些方法可能暫時不清楚,這不要緊,後面都會一 一介紹它們。dom
//使用filter,limit,forEach方法 list.stream().filter(s->!s.contains("two")).limit(4).forEach(System.out::println);
最終兩種方式的代碼運行結果是同樣的:ide
從上面使用Stream的狀況下咱們能夠發現是Stream直接鏈接成了一個串,而後將stream過濾後的結果轉爲集合並返回輸出。而至於轉成哪一種類型,這由JVM進行推斷。整個程序的執行過程像是在敘述一件事,沒有過多的中間變量,因此能夠減少GC壓力,提升效率。因此接下來開始探索Stream的功能吧。函數
Stream的原理是:將要處理的元素看作一種流,流在管道中傳輸,而且能夠在管道的節點上處理,包括有過濾、篩選、去重、排序、彙集等。元素在管道中通過中間操做的處理,最後由終止操做獲得前面處理的結果。
因此Stream的全部操做分爲這三個部分:建立對象-->中間操做-->終止操做,以下圖。
(1)、建立Stream:一個數據源(如:集合、數組),獲取一個流。
(2)、中間操做:一箇中間操做鏈,對數據源的數據進行處理。
(3)、終止操做(終端操做):一個終止操做,執行中間操做鏈,併產生結果。
①、使用Java8中被擴展的Collection接口,其中提供了兩個獲取流的方法:
//一、方法一:經過Collection ArrayList<String> list=new ArrayList<>(); //獲取順序流,即有順序的獲取 Stream<String> stream = list.stream(); //獲取並行流,即同時取數據,無序 Stream<String> stream1 = list.parallelStream();
②、使用Java8 中Arrays的靜態方法stream()獲取數組流:
//二、方法二:經過數組 String str[]=new String[]{"A","B","C","D","E"}; Stream<String> stream2 = Arrays.stream(str);
③、使用Stream類自己的靜態方法of(),經過顯式的賦來值建立一個流,它能夠接受任意數量的參數:
//三、方法三:經過Stream的靜態方法of() Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6);
④、建立無限流(迭代、生成)
//4.、方式四:建立無限流(迭代、生成) //迭代(須要傳入一個種子,也就是起始值,而後傳入一個一元操做) Stream.iterate(2, (x) -> x * 2).limit(10).forEach(System.out::println); //生成(無限產生對象) Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);
以上的四種方式學習前三中便可,第四種不經常使用。除了上面這些額外還有其餘建立Stream對象的方式,如Stream.empty():建立一個空的流、Random.ints():隨機數流等等。
中間操做的全部操做會返回一個新的流,但它不會修改原始的數據源,而且操做是延遲執行的(lazy),意思就是在終止操做開始的時候才中間操做才真正開始執行。
中間操做主要有如下方法:filter、limit、 skip、distinct、map (mapToInt, flatMap 等)、 sorted、 peek、 parallel、 sequential、 unordered等。
爲了更好地舉例,咱們先建立一個Student類:
public class Student { private int id; private String name; private int age; private String address; public Student() { } public Student(int id, String name, int age, String address) { this.id = id; this.name = name; this.age = age; this.address = address; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return id == student.id && age == student.age && Objects.equals(name, student.name) && Objects.equals(address, student.address); } @Override public int hashCode() { return Objects.hash(id, name, age, address); } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } }
後面的全部測試數據用會用下面這一組數據(必要時會改):
List<Student> students= Arrays.asList( new Student(1, "張三", 18, "北京"), new Student(2, "李四", 19, "上海"), new Student(3, "王五", 20, "廣州"), new Student(4, "趙六", 21, "浙江"), new Student(5, "孫七", 22, "深圳") );
而後開始介紹流的中間操做的方法使用:
①、filter(Predicate<? super T> predicate):篩選:接收 Lambda表達式,表示從流中過濾某些元素。
//篩選年齡大於19歲而且住在浙江的學生 students.stream().filter(s -> s.getAge() > 19).filter(s -> "浙江".equals(s.getAddress())).forEach(System.out::println);
運行結果爲:
這裏咱們建立了五個學生對象,而後通過filter的篩選,篩選出年齡大於19歲而且家庭住址是在浙江的學生集合。
②、limit(long maxSize):截斷:表示使其元素不超過給定數量。
//給定獲取3個數據 students.stream().limit(3).forEach(System.out::println);
運行結果:
咱們只讓它截斷3個,每次都是從第一個數據開始截取。
③、skip(long n):跳過:表示跳過前 n 個元素。若流中元素不足 n 個,則返回一個空流。它與 limit(n)互補。
//跳過前3個數據 students.stream().skip(3).forEach(System.out::println);
運行結果:
能夠發現輸出的數據剛好與limit方法互補。
④、distinct():篩選:去除流中重複的元素。
這裏我將第二個數據改爲和第一個同樣,看結果會怎樣。
public static void main(String [] args) { List<Student> students= Arrays.asList( new Student(1, "張三", 18, "北京"), new Student(1, "張三", 18, "北京"), new Student(3, "王五", 20, "廣州"), new Student(4, "趙六", 21, "浙江"), new Student(5, "孫七", 22, "深圳") ); //去除重複元素 students.stream().distinct().forEach(System.out::println); } }
運行結果:
能夠發現相同的元素被去除了,可是注意:distinct 須要實體中重寫hashCode()和 equals()方法纔可使用。
⑤、map(Function<? super T, ? extends R> mapper):映射(轉換):將一種類型的流轉換爲另一種類型的流。
爲了便於理解咱們來分析一下,map函數中須要傳入一個實現Function<T,R>函數式接口的對象,而該接口的抽象方法apply接收T類型,返回是R類型,因此map也能夠理解爲映射關係。
//lambda表達式 students.stream().map(s->s.getName()).forEach(System.out::println); //方法引用 students.stream().map(Student::getName).forEach(System.out::println);
運行結果:
上面的例子中將Student對象轉換爲了普通的String對象,獲取Student對象的名字。
⑥、flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):轉換合併:將流中的每一個值都轉換成另外一個流,而後把全部流組合成一個流而且返回。
它和map有點相似。flatMap在接收到Stream後,會將接收到的Stream中的每一個元素取出來放入另外一個Stream中,最後將一個包含多個元素的Stream返回。
List<Student> student1= Arrays.asList( new Student(1, "張三", 18, "北京"), new Student(2, "李四", 19, "上海") ); List<Student> student2= Arrays.asList( new Student(3, "王五", 20, "廣州"), new Student(4, "趙六", 21, "浙江"), new Student(5, "孫七", 22, "深圳") ); //lambda Stream.of(student1,student2).flatMap(student -> student.stream()).forEach(System.out::println); //方法引用 Stream.of(student1,student2).flatMap(List<Student>::stream).forEach(System.out::println); //常規方法 Stream.of(student1,student2).flatMap(new Function<List<Student>, Stream<?>>() { @Override public Stream<?> apply(List<Student> students) { return students.stream(); } }).forEach(System.out::println);
運行結果都是:
由於flatMap中最後須要將每一個元素組合成一個流,因此flatMap方法形參返回了一個stream流。
若是map和flatmap還不清楚能夠參考這篇博客:java8 stream流操做的flatMap(流的扁平化) 寫的很清楚。
⑦、sorted():天然排序:按天然順序排序的流元素。
這裏就不用Student對象做爲舉例了,不然要在Student類中實現Comparable接口。
//天然排序 List<String> list = Arrays.asList("CC", "BB", "EE", "AA", "DD"); list.stream().sorted().forEach(System.out::println);
運行結果:
上面使用String中默認實現的接口自動完成排序。
⑧、sorted(Comparator<? super T> comparator):自定排序:按提供的比較符排序的流元素 。
//自定排序 students.stream().sorted((s1,s2)-> { if(s1.getId()>s2.getId()){ return s1.getId().compareTo(s2.getId()); }else{ return -s1.getAge().compareTo(s2.getAge()); } }).forEach(System.out::println);
運行結果:
上面的代碼表示爲先按照id進行排序,若是id相同則按照年齡降序排序,你能夠將其餘id改成 1測試一下。
終止操做主要有如下方法:forEach、 forEachOrdered、anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、count、min、 max、 reduce、 collect、toArray、iterator。下面只介紹一下經常使用的方法。
注意:Stream流只要進行了終止操做後,就不能再次使用了,再次使用得從新建立流。
①、void forEach(Consumer<? super T> action):遍歷:接收Lambda表達式,遍歷流中的全部元素。
forEach上面已經用的夠多的了,這裏就不說了。
②、anyMatch/allMatch/noneMatch:匹配:返回值爲boolean,參數爲(Predicate<? super T> predicate)。
boolean b = students.stream().allMatch((s) -> s.getAge() > 20); System.out.println("allMatch()"+"\t"+b); boolean b1 = students.stream().anyMatch((s) -> s.getAge() > 21); System.out.println("anyMatch()"+"\t"+b1); boolean b2 = students.stream().noneMatch((s) -> s.getAge() == 22); System.out.println("noMatch()"+"\t"+b2);
運行結果:
③、findFirst/findAny:查找:返回Optional<T>,無參數。
//先按id排好序 Optional<Student> first = students.stream().sorted((s1, s2) -> s1.getId().compareTo(s2.getId())).findFirst(); System.out.println("findFirst()"+"\t"+first.get()); Optional<Student> any = students.stream().filter((s)->s.getAge()>19).findAny(); System.out.println("findAny()"+"\t"+any.get());
運行結果:
④、long count():統計:返回流中元素的個數。
//統計流中數量 long count = students.stream().count(); System.out.println("count()"+"\t"+count);
運行結果:
⑤、max/min:大小值:返回Optional<T>,無參數。
//獲取年齡最大值 Optional<Student> max = students.stream().max((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge())); System.out.println("max()"+"\t"+max.get()); //獲取年齡最小值 Optional<Student> min = students.stream().min((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge())); System.out.println("min()"+"\t"+min.get());
運行結果:
⑥、reduce:歸約:能夠將流中元素反覆結合在一塊兒,獲得一個值。
//累加0-10 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Integer sum = list.stream().reduce(0, (x, y) -> x + y); //一、T reduce(T identity, BinaryOperator<T> accumulator); System.out.println("reduce1--"+sum); //二、Optional<T> reduce(BinaryOperator<T> accumulator); Optional<Integer> sum1 = list.stream().reduce(Integer::sum); System.out.println("reduce2--"+sum1.get()); Optional<Integer> sum2 = list.stream().reduce(new BinaryOperator<Integer>() { @Override public Integer apply(Integer integer1, Integer integer2) { return Integer.sum(integer1,integer2); } }); System.out.println("reduce3--"+sum2.get());
運行結果:
備註:map和reduce的鏈接一般稱爲map-reduce模式,因Google用它來進行網絡搜索而出名。
Stream流與Collectors中的方法是很是好的搭檔,經過組合來以更簡單的方式來實現更增強大的功能,咱們利用Stream中的collect方法來實現。
collect:收集:將流轉換爲其餘形式。它接收一個 Collector接口的實現,用於給Stream中元素作彙總的方法。
Collector接口中方法得實現決定了如何對流執行收集操做(如收集到List,Set,Map)。可是Collectors實用類提供了不少靜態方法,能夠方便地建立常見得收集器實例。
因此下面逐一介紹Collectors中的方法:
①、Collectors.toList():將流轉換成List。
/** * Collectors.toList():將流轉換成List */ List<String> list = students.stream().map(student -> student.getName()).collect(Collectors.toList()); System.out.println("toList----"+list);
②、Collectors.toSet():將流轉換爲Set。
/** * Collectors.toSet():將流轉換成Set */ Set<String> set = students.stream().map(student -> student.getName()).collect(Collectors.toSet()); System.out.println("toSet----"+set);
③、Collectors.toCollection():將流轉換爲其餘類型的集合。
/** * Collectors.toCollection():將流轉換爲其餘類型的集合 */ TreeSet<String> treeSet = students.stream().map(student -> student.getName()).collect(Collectors.toCollection(TreeSet::new)); System.out.println("toCollection----"+treeSet);
④、Collectors.counting():元素個數。
/** * Collectors.counting():元素個數 */ Long aLong = students.stream().collect(Collectors.counting()); System.out.println("counting----"+aLong);
⑤、Collectors.averagingInt()、Collectors.averagingDouble()、Collectors.averagingLong():求平均數。
這三個方法均可以求平均數,不一樣之處在於傳入得參數類型不一樣,可是返回值都爲Double。
/** * Collectors.averagingInt() * Collectors.averagingDouble() * Collectors.averagingLong() * 求平均數 */ Double aDouble = students.stream().collect(Collectors.averagingInt(student -> student.getAge())); System.out.println("averagingInt()----"+aDouble); Double aDouble1 = students.stream().collect(Collectors.averagingDouble(student -> student.getAge())); System.out.println("averagingDouble()----"+aDouble1); Double aDouble2 = students.stream().collect(Collectors.averagingLong(student -> student.getAge())); System.out.println("averagingLong()----"+aDouble2);
⑥、Collectors.summingDouble()、Collectors.summingDouble()、Collectors.summingLong():求和。
這三個方法均可以求和,不一樣之處在於傳入得參數類型不一樣,返回值爲Integer, Double, Long。
/** * Collectors.summingInt() * Collectors.summingDouble() * Collectors.summingLong() * 求和 */ Integer integer = students.stream().collect(Collectors.summingInt(student -> student.getAge())); System.out.println("summingInt()----"+integer); Double aDouble3 = students.stream().collect(Collectors.summingDouble(student -> student.getAge())); System.out.println("summingDouble()----"+aDouble3); Long aLong1 = students.stream().collect(Collectors.summingLong(student -> student.getAge())); System.out.println("summingLong()----"+aLong1);
⑦、Collectors.maxBy():求最大值。
/** * Collectors.maxBy():求最大值 */ Optional<Integer> integer1 = students.stream().map(student -> student.getId()).collect(Collectors.maxBy((x, y) -> Integer.compare(x, y))); System.out.println("maxBy()----"+integer1.get());
⑧、Collectors.minBy():求最小值。
/** * Collectors.minBy():求最小值 */ Optional<Integer> integer2 = students.stream().map(student -> student.getId()).collect(Collectors.minBy((x, y) -> Integer.compare(x, y))); System.out.println("maxBy()----"+integer2.get());
⑨、Collectors.groupingBy():分組 ,返回一個map。
/** * Collectors.groupingBy():分組 ,返回一個map */ Map<Integer, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getId)); System.out.println(listMap);
其中Collectors.groupingBy()還能夠實現多級分組,以下:
/** * Collectors.groupingBy():多級分組 ,返回一個map */ //先按name分組而後得出每組的學生數量,使用重載的groupingBy方法,第二個參數是分組後的操做 Map<String, Long> stringLongMap = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting())); System.out.println("groupingBy()多級分組----"+stringLongMap); //先按id分組而後再按年齡分組 Map<Integer, Map<Integer, List<Student>>> integerMapMap = students.stream().collect(Collectors.groupingBy(Student::getId, Collectors.groupingBy(Student::getAge))); System.out.println("groupingBy()多級分組----"+integerMapMap); //遍歷 Map<Integer,List<Student>> map = new HashMap<>(); Iterator<Map.Entry<Integer, Map<Integer, List<Student>>>> iterator = integerMapMap.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<Integer, Map<Integer, List<Student>>> entry = iterator.next(); System.out.println("key="+entry.getKey()+"----value="+entry.getValue()); }
⑩、Collectors.partitioningBy():分區。按true和false分紅兩個區。
/** * Collectors.partitioningBy():分區,分紅兩個區 */ Map<Boolean, List<Student>> listMap1 = students.stream().collect(Collectors.partitioningBy(student -> student.getAge() > 18)); System.out.println(listMap1);
⑪、Collectors.joining():拼接。按特定字符將數據拼接起來。
/** * Collectors.joining():拼接 */ String str = students.stream().map(student -> student.getName()).collect(Collectors.joining("---")); System.out.println(str);
Java8中的Stream提供的功能很是強大,用它們來操做集合會讓咱們用更少的代碼,更快的速度遍歷出集合。並且在java.util.stream包下還有個類Collectors,它和stream是好很是好的搭檔,經過組合來以更簡單的方式來實現更增強大的功能。雖然上面介紹的都是Stream的一些基本操做,可是隻要你們勤加練習就能夠靈活使用,很快的能夠運用到實際應用中。