JDK新特性——Stream程式碼簡潔之道的詳細用法

牧小農發表於2020-10-24

一、概述

Stream 是一組用來處理陣列、集合的API,Stream API 提供了一種高效且易於使用的處理資料的方式。
Java 8 中之所以費這麼大的功夫引入 函數語言程式設計 ,原因有兩個:

  • 程式碼簡潔函數語言程式設計寫出的程式碼簡潔且意圖明確,使用stream介面讓你從此告別for迴圈。
  • 多核友好,Java函數語言程式設計使得編寫並行程式從未如此簡單,你需要的全部就是用用一下parallel()方法
  • Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作

二、Stream特性

1、不是資料結構,沒有內部儲存,不會儲存資料,故每個Stream流只能使用一次
2、不支援索引訪問
3、支援並行
4、很容易生成資料或集合(List,Set)
5、支援過濾、查詢、轉換、彙總、聚合等操作
6、延遲計算,流在中間處理過程中,只是對操作進行了記錄,並不會立即執行,需要等到執行終止操作的時候才會進行實際的計算

三、分類

關於應用在Stream流上的操作,可以分成兩種:

  1. Intermediate(中間操作): 中間操作的返回結果都是Stream,故可以多箇中間操作疊加;
  2. Terminal(終止操作): 終止操作用於返回我們最終需要的資料,只能有一個終止操作。

使用Stream流,可以清楚地知道我們要對一個資料集做何種操作,可讀性強。而且可以很輕鬆地獲取並行化Stream流,不用自己編寫多執行緒程式碼,可以讓我們更加專注於業務邏輯。

在這裡插入圖片描述
無狀態: 指元素的處理不受之前元素的影響;
有狀態: 指該操作只有拿到所有元素之後才能繼續下去。
非短路操作: 指必須處理所有元素才能得到最終結果;
短路操作: 指遇到某些符合條件的元素就可以得到最終結果,如 A || B,只要A為true,則無需判斷B的結果。

四、Stream的建立

1、通過陣列來生成
2、通過集合來生成
3、通過Stream.generate方法來建立
4、通過Stream.iterate方法來建立
5、其他Api建立

4.1 通過陣列來生成

 //通過陣列來生成
    static void gen1(){
        String[] strs = {"a","b","c","d"};
        Stream<String> strs1 = Stream.of(strs);//使用Stream中的靜態方法:of()
        strs1.forEach(System.out::println);//列印輸出(a、b、c、d)
    }

4.2 通過集合來生成

//通過集合來生成
    static void gen2(){
        List<String> list = Arrays.asList("1","2","3","4");
        Stream<String> stream = list.stream();//獲取一個順序流
        stream.forEach(System.out::println);//列印輸出(1,2,3,4)
    }

4.3 通過Stream.generate方法來建立

    //generate
    static void gen3(){
        Stream<Integer> generate = Stream.generate(() -> 1);//使用Stream中的靜態方法:generate()
        //limit 返回由該流的元素組成的流,截斷長度不能超過maxSize
        generate.limit(10).forEach(System.out::println);//列印輸出(列印10個1)
    }

4.4 通過Stream.iterate方法來建立

    //使用iterator
    static void gen4() {
        Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);//使用Stream中的靜態方法:iterate()
        iterate.limit(10).forEach(System.out::println);//列印輸出(1,2,3,4,5,6,7,8,9,10)
    }

4.4 其他Api建立

//其他方式
    static void gen5(){
        String str = "abcdefg";
        IntStream stream =str.chars();//獲取str 位元組碼
        stream.forEach(System.out::println);//列印輸出(97,98,99,100,101,102,103)
    }

五、Stream的常用API

5.1 中間操作

1. filter: 過濾流中的某些元素
 //中間操作:如果呼叫方法之後返回的結果是Stream物件就意味著是一箇中間操作
  Arrays.asList(1,2,3,4,5).stream()//獲取順序流
  .filter((x)->x%2==0) // 2 4 
  .forEach(System.out::println);

 //求出結果集中所有偶數的和
 int count = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9).stream()//獲取順序流
 .filter(x -> x % 2 == 0).// 2 4 6 8 
 mapToInt(x->x).sum();//求和
 System.out.println(count); //列印輸出 20 
2. distinct: 通過流中元素的 hashCode() 和 equals() 去除重複元素
 Arrays.asList(1,2,3,3,3,4,5,2).stream()//獲取順序流
 	.distinct()//去重
 	.forEach(System.out::println);// 列印輸出(1,2,3,4,5)
 
 System.out.println("去重:---------------");
 
 Arrays.asList(1,2,3,3,3,4,5,2).stream()//獲取順序流
 	.collect(Collectors.toSet())//Set()去重
 	.forEach(System.out::println);// 列印輸出(1,2,3,4,5)

3. 排序

sorted():返回由此流的元素組成的流,根據自然順序排序。
sorted(Comparator com):返回由該流的元素組成的流,根據提供的 Comparator進行排序。

//獲取最大值和最小值但是不使用min和max方法
   List<Integer> list = Arrays.asList(1,2, 3,4, 5, 6);
   Optional<Integer> min = list.stream().sorted().findFirst();//自然排序 根據數字從小到大排列
   System.out.println(min.get());//列印輸出(1)
   
   Optional<Integer> max2 = list.stream().sorted((a, b) -> b - a).findFirst();//定時排序 根據最大數進行排序
   System.out.println(max2.get());//列印輸出(6)

 //按照大小(a-z)排序
 Arrays.asList("java","c#","python","scala").stream().sorted().forEach(System.out::println);
 //按照長度排序
 Arrays.asList("java","c#","python","scala").stream().sorted((a,b)->a.length()-b.length()).forEach(System.out::println);
4. 擷取

limit(n):返回由此流的元素組成的流,截短長度不能超過 n
skip(n):在丟棄流的第n元素後,配合limit(n)可實現分頁

//列印20-30這樣的集合資料
      Stream.iterate(1,x->x+1).limit(50)// limit 50 總共到50
      .skip(20)// 跳過前 20
      .limit(10) // 列印10個
      .forEach(System.out::println);//列印輸出(21,22,23,24,25,26,27,28,29,30)
5. 轉換

map:接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素。
flatMap:接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流。

List<String> list = Arrays.asList("a,b,c", "1,2,3");
 
//將每個元素轉成一個新的且不帶逗號的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc  123
 
Stream<String> s3 = list.stream().flatMap(s -> {
    //將每個元素轉換成一個stream
    String[] split = s.split(",");
    Stream<String> s2 = Arrays.stream(split);
    return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3
6. 消費

peek:如同於map,能得到流中的每一個元素。但map接收的是一個Function表示式,有返回值;而peek接收的是Consumer表示式,沒有返回值。

 //將str中的每一個數值都列印出來,同時算出最終的求和結果
 String str ="11,22,33,44,55";      
 System.out.println(Stream.of(str.split(",")).peek(System.out::println).mapToInt(Integer::valueOf).sum());//11 22 33 44 55 165

5.2 終止操作

1. 迴圈:forEach

Users類:

import java.util.Date;

/**
 * @program: lambda
 * @ClassName Users
 * @description:
 * @author: muxiaonong
 * @create: 2020-10-24 11:00
 * @Version 1.0
 **/
public class Users {

    private String name;
    public Users() {}

    /**
     * @param name
     */
    public Users(String name) {
        this.name = name;
    }

    /**
     * @param name
     * @return
     */
    public static Users build(String name){
        Users u = new Users();
        u.setName(name);
        return u;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return  "name='" + name + '\'';
    }
  }
   //建立一組自定義物件
   String str2 = "java,scala,python";
   Stream.of(str2.split(",")).map(x->new Users(x)).forEach(System.out::println);//列印輸出(name='java' name='scala' name='python')
   Stream.of(str2.split(",")).map(Users::new).forEach(System.out::println);//列印輸出(name='java' name='scala' name='python')
   Stream.of(str2.split(",")).map(x->Users.build(x)).forEach(System.out::println);//列印輸出(name='java' name='scala' name='python')
   Stream.of(str2.split(",")).map(Users::build).forEach(System.out::println);//列印輸出(name='java' name='scala' name='python')

2. 計算:min、max、count、sum

min:返回流中元素最小值
max:返回流中元素最大值
count:返回流中元素的總個數
sum:求和

 //求集合中的最大值
 List<Integer> list = Arrays.asList(1,2, 3,4, 5, 6);
  Optional<Integer> max = list.stream().max((a, b) -> a - b);
  System.out.println(max.get()); // 6 
  //求集合的最小值
  System.out.println(list.stream().min((a, b) -> a-b).get()); // 1
 //求集合的總個數
 System.out.println(list.stream().count());//6
  //求和
  String str ="11,22,33,44,55";
  System.out.println(Stream.of(str.split(",")).mapToInt(x -> Integer.valueOf(x)).sum());
  System.out.println(Stream.of(str.split(",")).mapToInt(Integer::valueOf).sum());
  System.out.println(Stream.of(str.split(",")).map(x -> Integer.valueOf(x)).mapToInt(x -> x).sum());
  System.out.println(Stream.of(str.split(",")).map(Integer::valueOf).mapToInt(x -> x).sum());
3. 匹配:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny

anyMatch:接收一個 Predicate 函式,只要流中有一個元素滿足該斷言則返回true,否則返回false
allMatch:接收一個 Predicate 函式,當流中每個元素都符合該斷言時才返回true,否則返回false
noneMatch:接收一個 Predicate 函式,當流中每個元素都不符合該斷言時才返回true,否則返回false
findFirst:返回流中第一個元素
findAny:返回流中的任意元素

List<Integer> list = Arrays.asList(1,2, 3,4, 5, 6);
System.out.println(list.stream().allMatch(x -> x>=0)); //如果集合中的元素大於等於0 返回true
System.out.println(list.stream().noneMatch(x -> x > 5));//如果集合中的元素有大於5的元素。返回false
System.out.println(list.stream().anyMatch(x -> x > 4));//如果集合中有大於四4的元素,返回true
//取第一個偶數
Optional<Integer> first = list.stream().filter(x -> x % 10 == 6).findFirst();
System.out.println(first.get());// 6
//任意取一個偶數
Optional<Integer> any = list.stream().filter(x -> x % 2 == 0).findAny();
System.out.println(any.get());// 2

4.收集器:toArray、collect

collect:接收一個Collector例項,將流中元素收整合另外一個資料結構
Collector<T, A, R> 是一個介面,有以下5個抽象方法:

  1. Supplier<A> supplier();建立一個結果容器A
  2. BiConsumer<A, T> accumulator();:消費型介面,第一個引數為容器A,第二個引數為流中元素T。
  3. BinaryOperator<A> combiner();函式介面,該引數的作用跟上一個方法(reduce)中的combiner引數一樣,將並行流中各個子程式的執行結果(accumulator函式操作後的容器A)進行合併。
  4. Function<A, R> finisher();函式式介面,引數為:容器A,返回型別為:collect方法最終想要的結果R。
  5. Set<Characteristics> characteristics();返回一個不可變的Set集合,用來表明該Collector的特徵
/**
 * @program: lambda
 * @ClassName Customer
 * @description:
 * @author: muxiaonong
 * @create: 2020-10-24 11:36
 * @Version 1.0
 **/
public class Customer {

    private String name;

    private Integer age;
    
...getset忽略
}
 public static void main(String[] args) {
        Customer c1 = new Customer("張三",10);
        Customer c2 = new Customer("李四",20);
        Customer c3 = new Customer("王五",10);

        List<Customer> list = Arrays.asList(c1,c2,c3);

        //轉成list
        List<Integer> ageList = list.stream().map(Customer::getAge).collect(Collectors.toList());
        System.out.println("ageList:"+ageList);//ageList:[10, 20, 10]

        //轉成set
        Set<Integer> ageSet = list.stream().map(Customer::getAge).collect(Collectors.toSet());
        System.out.println("ageSet:"+ageSet);//ageSet:[20, 10]

//轉成map,注:key不能相同,否則報錯
        Map<String, Integer> CustomerMap = list.stream().collect(Collectors.toMap(Customer::getName, Customer::getAge));
        System.out.println("CustomerMap:"+CustomerMap);//CustomerMap:{李四=20, 張三=10, 王五=10}

//字串分隔符連線
        String joinName = list.stream().map(Customer::getName).collect(Collectors.joining(",", "(", ")"));
        System.out.println("joinName:"+joinName);//joinName:(張三,李四,王五)

//聚合操作
//1.學生總數
        Long count = list.stream().collect(Collectors.counting());
        System.out.println("count:"+count);//count:3
//2.最大年齡 (最小的minBy同理)
        Integer maxAge = list.stream().map(Customer::getAge).collect(Collectors.maxBy(Integer::compare)).get();
        System.out.println("maxAge:"+maxAge);//maxAge:20

//3.所有人的年齡
        Integer sumAge = list.stream().collect(Collectors.summingInt(Customer::getAge));
        System.out.println("sumAge:"+sumAge);//sumAge:40

//4.平均年齡
        Double averageAge = list.stream().collect(Collectors.averagingDouble(Customer::getAge));
        System.out.println("averageAge:"+averageAge);//averageAge:13.333333333333334

//分組
        Map<Integer, List<Customer>> ageMap = list.stream().collect(Collectors.groupingBy(Customer::getAge));
        System.out.println("ageMap:"+ageMap);//ageMap:{20=[com.mashibing.stream.Customer@20ad9418], 10=[com.mashibing.stream.Customer@31cefde0, com.mashibing.stream.Customer@439f5b3d]}


//分割槽
//分成兩部分,一部分大於10歲,一部分小於等於10歲
        Map<Boolean, List<Customer>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
        System.out.println("partMap:"+partMap);

//規約
        Integer allAge = list.stream().map(Customer::getAge).collect(Collectors.reducing(Integer::sum)).get();
        System.out.println("allAge:"+allAge);//allAge:40


    }

六、Stream的方法摘要

修飾符和型別 方法和說明
static Collector<T,?,Double> averagingDouble(ToDoubleFunction<? super T> mapper) 返回一個 Collector ,它產生應用於輸入元素的雙值函式的算術平均值。
static Collector<T,?,Double> averagingInt(ToIntFunction<? super T> mapper) 返回一個 Collector ,它產生應用於輸入元素的整數值函式的算術平均值。
static Collector<T,?,Double> averagingLong(ToLongFunction<? super T> mapper) 返回一個 Collector ,它產生應用於輸入元素的長值函式的算術平均值。
static <T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher) 適應 Collector進行額外的整理轉換。
static Collector<T,?,Long> counting() 返回 Collector型別的接受元件 T計數輸入元件的數量。
static <T,K> Collector<T,?,Map<K,List>> groupingBy(Function<? super T,? extends K> classifier) 返回 Collector “由基團”上的型別的輸入元件操作實現 T ,根據分類功能分組元素,並且在返回的結果 Map 。
static <T,K,A,D> Collector<T,?,Map<K,D>> groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream) 返回 Collector “由基團”上的型別的輸入元件操作實現級聯 T ,根據分類功能分組元素,然後使用下游的指定執行與給定鍵相關聯的值的歸約運算 Collector 。
static <T,K,D,A,M extends Map<K,D>>Collector<T,?,M> groupingBy(Function<? super T,? extends K> classifier, Supplier mapFactory, Collector<? super T,A,D> downstream) 返回 Collector “由基團”上的型別的輸入元件操作實現級聯 T ,根據分類功能分組元素,然後使用下游的指定執行與給定鍵相關聯的值的歸約運算 Collector 。
static <T,K> Collector<T,?,ConcurrentMap<K,List>> groupingByConcurrent(Function<? super T,? extends K> classifier) 返回一個併發 Collector “由基團”上的型別的輸入元件操作實現 T ,根據分類功能分組元素。
static <T,K,A,D> Collector<T,?,ConcurrentMap<K,D>> groupingByConcurrent(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream) 返回一個併發 Collector “由基團”上的型別的輸入元件操作實現級聯 T ,根據分類功能分組元素,然後使用下游的指定執行與給定鍵相關聯的值的歸約運算 Collector 。
static <T,K,A,D,M extends ConcurrentMap<K,D>> Collector<T,?,M> groupingByConcurrent(Function<? super T,? extends K> classifier, Supplier mapFactory, Collector<? super T,A,D> downstream) 返回一個併發 Collector “由基團”上的型別的輸入元件操作實現級聯 T ,根據分類功能分組元素,然後使用下游的指定執行與給定鍵相關聯的值的歸約運算 Collector 。
static Collector<CharSequence,?,String> joining() 返回一個 Collector ,按照遇到的順序將輸入元素連線到一個 String中。
static Collector<CharSequence,?,String> joining(CharSequence delimiter) 返回一個 Collector ,按照遇到的順序連線由指定的分隔符分隔的輸入元素。
static Collector<CharSequence,?,String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 返回一個 Collector ,它將按照指定的 Collector分隔的輸入元素與指定的字首和字尾進行連線。
static <T,U,A,R> Collector<T,?,R> mapping(Function<? super T,? extends U> mapper, Collector<? super U,A,R> downstream) 適應一個 Collector型別的接受元件 U至型別的一個接受元件 T通過積累前應用對映函式到每個輸入元素。
static Collector<T,?,Optional> maxBy(Comparator<? super T> comparator) 返回一個 Collector ,它根據給出的 Comparator產生最大元素,描述為 Optional
static Collector<T,?,Optional> minBy(Comparator<? super T> comparator) 返回一個 Collector ,根據給出的 Comparator產生最小元素,描述為 Optional
static Collector<T,?,Map<Boolean,List>> partitioningBy(Predicate<? super T> predicate) 返回一個 Collector ,根據Predicate對輸入元素進行 Predicate ,並將它們組織成 Map<Boolean, List> 。
static <T,D,A> Collector<T,?,Map<Boolean,D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T,A,D> downstream) 返回一個 Collector ,它根據Predicate對輸入元素進行 Predicate ,根據另一個 Collector減少每個分割槽的值,並將其組織成 Map<Boolean, D> ,其值是下游縮減的結果。
static Collector<T,?,Optional> reducing(BinaryOperator op) 返回一個 Collector ,它在指定的 Collector下執行其輸入元素的 BinaryOperator 。
static Collector<T,?,T> reducing(T identity, BinaryOperator op) 返回 Collector執行下一個指定的減少其輸入元件的 BinaryOperator使用所提供的身份。
static <T,U> Collector<T,?,U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator op) 返回一個 Collector ,它在指定的對映函式和 BinaryOperator下執行其輸入元素的 BinaryOperator 。
static Collector<T,?,DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) 返回一個 Collector , double生產對映函式應用於每個輸入元素,並返回結果值的彙總統計資訊。
static Collector<T,?,IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) 返回一個 Collector , int生產對映函式應用於每個輸入元素,並返回結果值的彙總統計資訊。
static Collector<T,?,LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper) 返回一個 Collector , long生產對映函式應用於每個輸入元素,並返回結果值的彙總統計資訊。
static Collector<T,?,Double> summingDouble(ToDoubleFunction<? super T> mapper) 返回一個 Collector ,它產生應用於輸入元素的雙值函式的和。
static Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper) 返回一個 Collector ,它產生應用於輸入元素的整數值函式的和。
static Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper) 返回一個 Collector ,它產生應用於輸入元素的長值函式的和。
static <T,C extends Collection> Collector<T,?,C> toCollection(Supplier collectionFactory) 返回一個 Collector ,按照遇到的順序將輸入元素累加到一個新的 Collection中。
static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) 返回一個併發的 Collector ,它將元素累加到 ConcurrentMap ,其鍵和值是將所提供的對映函式應用於輸入元素的結果。
static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator mergeFunction) 返回一個併發的 Collector ,它將元素累加到一個 ConcurrentMap ,其鍵和值是將提供的對映函式應用於輸入元素的結果。
static <T,K,U,M extends ConcurrentMap<K,U>> Collector<T,?,M> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator mergeFunction, Supplier mapSupplier) 返回一個併發的 Collector ,它將元素累加到一個 ConcurrentMap ,其鍵和值是將所提供的對映函式應用於輸入元素的結果。
static Collector<T,?,List> toList() 返回一個 Collector ,它將輸入元素 List到一個新的 List 。
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) 返回一個 Collector ,它將元素累加到一個 Map ,其鍵和值是將所提供的對映函式應用於輸入元素的結果。
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator mergeFunction) 返回一個 Collector ,它將元素累加到 Map ,其鍵和值是將提供的對映函式應用於輸入元素的結果。
static <T,K,U,M extends Map<K,U>> Collector<T,?,M> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator mergeFunction, Supplier mapSupplier) 返回一個 Collector ,它將元素累加到一個 Map ,其鍵和值是將所提供的對映函式應用於輸入元素的結果。
static Collector<T,?,Set> toSet() 返回一個 Collector ,將輸入元素 Set到一個新的 Set 。

總結

對於Java中新特性除了 Stream 還有lamaba表示式都是可以幫忙我們很好的去優化程式碼,使我們的程式碼簡潔且意圖明確,避免繁瑣的重複性的操作,對於文中有興趣的小夥伴可以操作起來,又不懂的小夥伴可以在下面進行留言,小農看到了會第一時間回覆大家,謝謝,大家加油!

相關文章