Java 1.8 函數式編程詳解

Java 1.8 函數式編程詳解

一. 概述

1.1 java 8 新特性:

  • 概述:
    • Java 8 正式版是一個有重大改變的版本,該版本對 Java 做了重大改進。本文章主要講述java 1.8 函數式編程,主要涉及:函數式接口Lambda 表達式集合的流式操作;
  • 新特性如下:
    • 函數式接口
    • Lambda 表達式
    • 集合的流式操作
    • 註解
    • 安全性
    • IO/NIO
    • 全球化功能

二. 函數式接口

2.1 函數式接口概述

  • java 1.8 引入的一個核心概念是函數式接口(Functional Interfaces)。通過在接口裏面添加一個抽象方法,這些方法可以直接從接口中運行。如果一個接口定義個唯一一個抽象方法,那麼這個接口就成爲函數式接口。同時,引入了一個新的註解:@FunctionalInterface。可以把他它放在一個接口前,表示這個接口是一個函數式接口。這個註解是非必須的,只要接口只包含一個方法的接口,虛擬機會自動判斷,不過最好在接口上使用註解 @FunctionalInterface 進行聲明。在接口中添加了 @FunctionalInterface 的接口,只允許有一個抽象方法,否則編譯器也會報錯。
    • java.lang.Runnable就是一個函數式接口
      • 代碼如下:
        	@FunctionalInterface
        	public interface Runnable {
        	public abstract void run();
        	}
        
  • 函數式接口,總的來說它是指有且只有一個未實現的方法的接口,一般通過Functionallterface這個註解來表明某個接口是一個函數式接口。函數式接口是java支持函數式編程的基礎;

    函數式編程語法,能夠精簡代碼;

2.2 Lambda表達式概述

  • 函數式接口的重要屬性是:我們能夠使用 Lambda 實例化它們,Lambda 表達式讓你能夠將函數作爲方法參數,或者將代碼作爲數據對待。Lambda 表達式的引入給開發者帶來了不少優點:在 Java 8 之前,匿名內部類,監聽器和事件處理器的使用都顯得很冗長,代碼可讀性很差,Lambda 表達式的應用則使代碼變得更加緊湊,可讀性增強;Lambda 表達式使並行操作大集合變得很方便,可以充分發揮多核 CPU 的優勢,更易於爲多核處理器編寫代碼;
  • Lambda 表達式由三個部分組成:第一部分爲一個括號內用逗號分隔的形式參數,參數是函數式接口裏面方法的參數;第二部分爲一個箭頭符號:->;第三部分爲方法體,可以是表達式和代碼塊。語法如下:
    • 方法體爲表達式,該表達式的值作爲返回值返回

      (parameters)-> expression
      
    • 方法體爲代碼塊,必須用 {} 來包裹起來,且需要一個 return 返回值,但若函數式接口裏面方法返回值是 void,則無需返回值。

      (parameters) -> {statements;}
      

      例如,下面是使用匿名內部類和Lambda表達式的代碼比較。

    • 下面是使用匿名內部類的代碼:

      	button.addActionListener(new ActionListener() {
      		@Override
      		public void actionPerformed(ActionEvent e) {
      			System.out.print("Helllo Lambda in actionPerformed");
      		}
      	});
      	下面是使用 Lambda 表達式後:
      	button.addActionListener(
      		\\actionPerformed 有一個參數 e 傳入,所以用 (ActionEvent e)
      		(ActionEvent e)-> System.out.print("Helllo Lambda in actionPerformed")
      	);	
      
    • 上面是方法體包含了參數傳入 (ActionEvent e),如果沒有參數則只需 ( ),例如 Thread 中的 run 方法就沒有參數傳入,當它使用 Lambda 表達式後:

      	Thread t = new Thread(() -> { System.out.println("Hello from a thread in run");});   \\run 沒有參數傳入,所以用 (), 後面用 {} 包起方法體
      	通過上面兩個代碼的比較可以發現使用 Lambda 表達式可以簡化代碼,並提高代碼的可讀性。
      	爲了進一步簡化 Lambda 表達式,可以使用方法引用。例如,下面三種分別是使用內部類,使用 Lambda 表示式和使用方法引用方式的比較:
      	//1. 使用內部類
      	Function<Integer, String> f = new Function<Integer,String>(){
      	@Override
      	public String apply(Integer t) {
      		return null;
      		}
      	};
      	//2. 使用 Lambda 表達式
      	Function<Integer, String> f2 = (t)->String.valueOf(t); 
      	//3. 使用方法引用的方式
      	Function<Integer, String> f1 = String::valueOf;	
      
  • 要使用 Lambda 表達式,需要定義一個函數式接口,這樣往往會讓程序充斥着過量的僅爲 Lambda 表達式服務的函數式接口。爲了減少這樣過量的函數式接口,Java 8 在 java.util.function 中增加了不少新的函數式通用接口。例如:
    • Function<T, R>:將 T 作爲輸入,返回 R 作爲輸出,他還包含了和其他函數組合的默認方法。
    • Predicate :將 T 作爲輸入,返回一個布爾值作爲輸出,該接口包含多種默認方法來將 Predicate 組合成其他複雜的邏輯(與、或、非)。
    • Consumer :將 T 作爲輸入,不返回任何內容,表示在單個參數上的操作.
     例如,People 類中有一個方法 getMaleList 需要獲取男性的列表,這裏需要定義一個函數式接口 			PersonInterface:
    	interface PersonInterface {
    		 public boolean test(Person person);
    	}
    	public class People {
    		 private List<Person> persons= new ArrayList<Person>();
    		 public List<Person> getMaleList(PersonInterface filter) {
    		 List<Person> res = new ArrayList<Person>();
    		 persons.forEach(
    			 (Person person) -> {
    			 if (filter.test(person)) {//調用 PersonInterface 的方法
    			 res.add(person);}}
    	 		);
    	 	return res;
    		 }
    		}
    	// 爲了去除 PersonInterface 這個函數式接口,可以用通用函數式接口 Predicate 替代如下:
    	class People{
    		 private List<Person> persons= new ArrayList<Person>();
    	 	public List<Person> getMaleList(Predicate<Person> predicate) {
    		 List<Person> res = new ArrayList<Person>();
    		 persons.forEach(
    		 person -> {
    	 		if (predicate.test(person)) {//調用 Predicate 的抽象方法 test
    			 res.add(person);
    		 }});
    	 	return res;
    	 	}
    	}
    

2.3 java.util.function介紹

  • 概述: java.util.function它包含了很多類,用來支持java的函數式編程,該包中的函數式接口有:
    序號 接口 描述
    1 BiConsumer<T,U> 代表了一個接受兩個參數的操作,並且不返回任何結果
    2 BiFunction<T,U,R> 代表了一個接受兩個輸入參數的方法,並且返回一個結果
    3 BinaryOperator 代表了一個作用於於兩個同類型操作符的操作,並且返回了操作符同類型的結果
    4 BiPredicate<T,U> 代表了一個兩個參數的boolean值方法
    5 BooleanSupplier 代表了boolean值結果的提供方
    6 Consumer 代表了接受一個輸入參數並且無返回的操作
    7 DoubleBinaryOperator 代表了作用於兩個double值操作符的操作,並且返回了一個double值的結果。
    8 DoubleConsumer 代表一個接受double值參數的操作,並且不返回結果
    9 DoubleFunction 代表接受一個double值參數的方法,並且返回結果
    10 DoublePredicate 代表一個擁有double值參數的boolean值方法
    11 DoubleSupplier 代表一個double值結構的提供方
    12 DoubleToIntFunction 接受一個double類型輸入,返回一個int類型結果
    13 DoubleToLongFunction 接受一個double類型輸入,返回一個long類型結果
    14 DoubleUnaryOperator 接受一個參數同爲類型double,返回值類型也爲double 。
    15 Function<T,R> 接受一個輸入參數,返回一個結果。
    16 IntBinaryOperator 接受兩個參數同爲類型int,返回值類型也爲int
    17 IntConsumer 接受一個int類型的輸入參數,無返回值 。
    18 IntFunction 接受一個int類型輸入參數,返回一個結果 。
    19 IntPredicate 接受一個int輸入參數,返回一個布爾值的結果。
    20 IntSupplier 無參數,返回一個int類型結果。
    21 IntToDoubleFunction 接受一個int類型輸入,返回一個double類型結果 。
    22 IntToLongFunction 接受一個int類型輸入,返回一個long類型結果。
    23 IntUnaryOperator 接受一個參數同爲類型int,返回值類型也爲int 。
    24 LongBinaryOperator 接受兩個參數同爲類型long,返回值類型也爲long。
    25 LongConsumer 接受一個long類型的輸入參數,無返回值。
    26 LongFunction 接受一個long類型輸入參數,返回一個結果。
    27 LongPredicate R接受一個long輸入參數,返回一個布爾值類型結果。
    28 LongSupplier 無參數,返回一個結果long類型的值
    29 LongToDoubleFunction 接受一個long類型輸入,返回一個double類型結果
    30 LongToIntFunction 接受一個long類型輸入,返回一個int類型結果。
    31 LongUnaryOperator 接受一個參數同爲類型long,返回值類型也爲long。
    32 ObjDoubleConsumer 接受一個object類型和一個double類型的輸入參數,無返回值。
    33 ObjIntConsumer 接受一個object類型和一個int類型的輸入參數,無返回值。
    34 ObjLongConsumer 接受一個object類型和一個long類型的輸入參數,無返回值。
    35 Predicate 接受一個輸入參數,返回一個布爾值結果。
    36 Supplier 無參數,返回一個結果。
    37 ToDoubleBiFunction<T,U> 接受兩個輸入參數,返回一個double類型結果
    38 ToDoubleFunction 接受一個輸入參數,返回一個double類型結果
    39 ToIntBiFunction<T,U> 接受兩個輸入參數,返回一個int類型結果。
    40 ToIntFunction 接受一個輸入參數,返回一個int類型結果。
    41 ToLongBiFunction<T,U> 接受兩個輸入參數,返回一個long類型結果。
    42 ToLongFunction 接受一個輸入參數,返回一個long類型結果。
    43 UnaryOperator 接受一個參數爲類型T,返回值類型也爲T。

2.4 實戰演示函數式編程

  • Lambda初探:

    • 定義Student接口:
      public interface Student {
      	    void xuexi();
      	}
      
    • 實現此接口:
          public static void main(String[] args) {
              Student student=new Student() {
                  @Override
                  public void xuexi() {
                      System.err.println("學生的每天學習!");
                  }
              };
          }
      
      • 使用lambda表達式簡略:
      public static void main(String[] args) {
              Student student= () -> System.err.println("學生的每天學習!");
      }
      
         > 能使用lambda表達式的條件: 1. 是一個接口   2. 只有一個抽象方法    這裏因爲沒有參數所以括號內的內容爲空;
      
  • Lambda表達式,有參數初探:

    • 定義Student接口:
      public interface Student {
          void xuexi(String content);
      }
      
    • 實現此接口:
          public static void main(String[] args) {
          Student student= new Student() {
              @Override
              public void xuexi(String content) {
                  System.err.println("學生的"+content+"每天學習!");
              }
          };
          student.xuexi("任務是");
      }
      
    • 使用Lambda表達式優化:
          public static void main(String[] args) {
          Student student= content -> System.err.println("學生的"+content+"每天學習!");
          student.xuexi("任務是");
      }
      

    控制檯打印結果如下:
    在這裏插入圖片描述

  • 什麼是函數式接口:一個接口只有一個抽象方法,它就是函數式接口;

  • 什麼是函數式編程:滿足函數式接口,並且使用此接口用lambda表達式寫法時,就用到了函數式編程了;

上面示例已經說明,函數式編程接口都只有一個抽象方法,因此在採用這種寫法時,編譯器會將這段函數編譯後當做該抽象方法的實現。 如果接口與有多個抽象方法,編譯器就不知道這段函數式應該實現哪個方法了。

  • 語法:

    • 輸入-> 前面的部分,即被()包圍的部分。此處只有一個輸入參數,實際上輸入可以有多個的,如兩個參數時的寫法:(a,b);當然也可以沒有輸入,此時直接就可以是();
    • 函數體: ->後面的部分,即被{}包圍的部分;可以是一段代碼。
    • 輸出:函數式編程可以沒有返回值,也可以有返回值。如果有返回值時,需要代碼段的最後一句通過return的方式返回對應的值。
  • 簡略語法:

    • 當函數體中只有一個語句時,可以去掉{}進一步簡化:
      • 比如一段代碼:
        Consumer c = new Consumer() {
            @Override
            public void accept(Object o) {
                System.out.println(o);
            }
        };
        
      • 轉爲lambda後:
        Consumer c = (o) -> System.out.println(o);
        
    • 然而這還不是最簡的,由於此處只是進行打印,調用了System.out中的println靜態方法對輸入參數直接進行打印,因此可以簡化成以下寫法:
      Consumer c = System.out::println;
      

      System.out 表示要調用的哪個類 :: 後面的表示調用這個類的某個方法; 我們不但可以使用println,還可以使用這個類的其他方法進行替換;

  • 總結:
    通過最後一段代碼,我們可以簡單的理解函數式編程,Consumer接口直接就可以當成一個函數了,這個函數接收一個輸入參數,然後針對這個輸入進行處理;當然其本質上仍舊是一個對象,但我們已經省去了諸如老方式中的對象定義過程,直接使用一段代碼來給函數式接口對象賦值。
    而且最爲關鍵的是,這個函數式對象因爲本質上仍舊是一個對象,因此可以做爲其它方法的參數或者返回值,可以與原有的代碼實現無縫集成!

2.5 函數式接口之 Consumer

  • 概述: Consumer是一個函數式編程接口:顧名思義,Consumer的意思就是消費,即針對某個東西我們來使用它,因此它包含有一個有輸入而無輸出的accept接口方法;除accept方法,它還包含有andThen這個方法;
  • 其定義如下:
    default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
    }
    
  • 可見這個方法就是指定在調用當前Consumer後是否還要調用其它的Consumer;使用示例:
    public static void consumerTest() {
    Consumer f = System.out::println;
    Consumer f2 = n -> System.out.println(n + "-F2");
    
    //執行完F後再執行F2的Accept方法
    f.andThen(f2).accept("test");
    
    //連續執行F的Accept方法
    f.andThen(f).andThen(f).andThen(f).accept("test1");
    }
    
  • 實戰實例:
    • Student學生類
      /**
       * @program: 
       * @description:
       * @author: anyu
       * @create: 2020-03-09
       */
      @Data
      public class Student implements Serializable {
          private String name;
          private String age;
      }
      
    • 測試類:
          public static void main(String[] args) {
          Consumer consumer = x -> System.out.println(JSON.toJSONString(x));
          Student student = new Student();
          student.setAge("18");
          consumer.accept(student);
      }
      

      在不需要返回參數的情況下,我們可以使用Consumer接口來實現函數式編程;控制檯打印結果如圖:
      在這裏插入圖片描述

2.6 函數式接口之 Function

  • 概述:Function也是一個函數式編程接口;它代表的含義是“函數”,而函數經常是有輸入輸出的,因此它含有一個apply方法,包含一個輸入與一個輸出;\
  • 除apply方法外,它還有compose與andThen及indentity三個方法,其使用見下述示例;
    /**
     * Function測試
     */
    public static void functionTest() {
        Function<Integer, Integer> f = s -> s++;
        Function<Integer, Integer> g = s -> s * 2;
    
        /**
         * 下面表示在執行F時,先執行G,並且執行F時使用G的輸出當作輸入。
         * 相當於以下代碼:
         * Integer a = g.apply(1);
         * System.out.println(f.apply(a));
         */
        System.out.println(f.compose(g).apply(1));
    
        /**
         * 表示執行F的Apply後使用其返回的值當作輸入再執行G的Apply;
         * 相當於以下代碼
         * Integer a = f.apply(1);
         * System.out.println(g.apply(a));
         */
        System.out.println(f.andThen(g).apply(1));
    
        /**
         * identity方法會返回一個不進行任何處理的Function,即輸出與輸入值相等; 
         */
        System.out.println(Function.identity().apply("a"));
    }
    
  • 在使用函數式編程的時候,我們不但可以去操作對象,還可以操作數組,操作基本數據類型,操作集合等等;接下來給大家實戰示例一下:
    • 使用對象
          public static void main(String[] args) {
          Function<Student, String> g = Student::getName;
          Student student = new Student();
          student.setName("張三");
          String apply = g.apply(student);
          System.err.println(apply);
      }
      

      g 是定義的一個方法;第二步創建了一個對象;當把這個對象作爲參數調用apply方法執行時,就會獲得這個對象的名稱作爲String返回;拿到這個String返回時並打印到控制檯,如圖所示:
      在這裏插入圖片描述

    • 使用數組
          public static void main(String[] args) {
          Function<Student[], String> g = students->students[0].getName() ;           // 操作數組,打印數組中的Student對象的getName值
          
          Student student = new Student();       // 定義對象
          student.setName("暗餘");
          Student[] arr=new Student[]{student};       // 定義數組並將此對象傳入
          String apply = g.apply(arr);            // 執行Function方法並獲取到返回值
          System.err.println(apply);              // 打印返回值
      }
      
    • 使用 基本數據類型:
          public static void main(String[] args) {
          Function<Integer,Integer> function= x-> x+1;
          System.out.println(function.apply(5));
      }
      

      結果控制檯打印爲 : 6

    • 使用 集合:
          public static void main(String[] args) {
          Function<List<Student>,List<String>> function= x-> {
              List<String> list= Lists.newArrayList();
              for (Student student : x) {
                  list.add(student.getAge());
              }
              return list;
          };
      
          Student student1=new Student();
          student1.setAge("18");
          Student student2 =new Student();
          student2.setAge("19");
      
          List<Student> list=Lists.newArrayList();
          Collections.addAll(list, student1, student2);
      
      
          List<String> apply = function.apply(list);
          System.err.println(JSON.toJSONString(apply));
      }
      
      • 上面的Function方法,是將Student對象的List轉爲String集合,只提取每個對象中的Age內容;由於邏輯較多無法通過一行代碼完成。但是我們使用Stream流,可以繼續簡化。如下圖所示:
          public static void main(String[] args) {
      
           Function<List<Student>,List<String>> function= x->x.stream().map(y-> y.getAge()).collect(Collectors.toList());
      
           Student student1=new Student();
           student1.setAge("18");
           Student student2 =new Student();
           student2.setAge("19");
      
           List<Student> list=Lists.newArrayList();
           Collections.addAll(list, student1, student2);
      
      
           List<String> apply = function.apply(list);
           System.err.println(JSON.toJSONString(apply));
       }
      

      很酷很優雅有沒有 >< 下面將會爲大家介紹Stream流;

2.7 Function包結構介紹

  • 概述:當我們使用Function定義函數時,不但可以使用Function,還可以使用BiFunction以及BiConsumer等接口。

  • 常用函數式接口表如下:

    接口 描述
    Function<T,R> 接受一個輸入參數,返回一個結果
    Supplier 無參數,返回一個結果
    Consumer 接受一個輸入參數,並且不返回任何結果
    BiFunction<T,U,R> 接受兩個輸入參數的方法,並且返回一個結果
    BiConsumer<T,U> 接受兩個輸入參數的操作,並且不返回任何結果

三. Stream流處理

3.1 概述

  • 概述: 說到Stream便容易想到I/O Stream,而實際上,誰規定“流”就一定是“IO流”呢?在Java 8中,得益於Lambda所帶 來的函數式編程,引入了一個全新的Stream概念,用於解決已有集合類庫既有的弊端。
  • 傳統集合的多步遍歷代碼:幾乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或間接的遍歷操作。而當我們需要對集合中的元 素進行操作的時候,除了必需的添加、刪除、獲取外,典型的就是集合遍歷,在遍歷的過程中再對集合內的數據進行處理,代碼往往會寫很多。
  • 筆者概述:Stream流就是將集合放入一個工廠流水線中,進行一系列的如:過濾跳過映射轉換等操作,最後拿到我們需要的數據,如統計長度,新集合新map等。就是對一個集合進行一系列處理,最後直接拿到我們想要的結果,並且代碼量得到了大幅度的縮減,甚至一行代碼完成了平時二十行代碼達不到的效果!

3.2 Stream流對象的創建

  • Stream創建的方式有多種,分別如下:
    • 創建空的Stream對象
      Stream stream=Stream.empty();
      
    • 通過集合類中的stream或者parallelStream方法創建
      List<String> list = Arrays.asList("a", "b", "c", "d");
      Stream listStream = list.stream();                   //獲取串行的Stream對象
      Stream parallelListStream = list.parallelStream();   //獲取並行的Stream對象  
      
    • 通過Stream 中的of方法(可變參數)創建
      Stream s= Stream.of("test");
      Stream s1 =Stream.of("a","b","c","d");
      

      of 內可以接受多種類型,比如數組也可以接收,代碼如下:

      	        int [] a=new int[]{10,11};
         			Stream<int[]> a1 = Stream.of(a);
      
    • 通過Stream 中的iterate方法創建
      public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);  
      public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
      
    • 通過範圍創建數值流
      IntStream.range(0,100);			//不包含最後一個數
      IntStream.rangeClosed(0,99);		//包含最後一個數
      

3.3 Stream流對象的使用

  • 概述:Stream對象提供多個非常有用的方法,這些方法可以分成兩類:

    • 中間操作:將原始的Stream轉換成另外一個Stream;如filter返回的是過濾後的Stream。
    • 終端操作:產生的是一個結果或者其它的複合操作;如count或者forEach操作。

    通過調用中間操作的方法,我們可以將Stream中的數據進行一系列的操作,如過濾跳過,求最大值,最小值排序轉換等一系列的過濾或轉換或聚類等;

  • 其清單如下所示,方法的具體說明及示例等請見後面:

  • 中間操作如表:

    方法 說明
    sequential 返回一個相等的串行的Stream對象,如果原Stream對象已經是串行就可能會返回原對象
    parallel 返回一個相等的並行的Stream對象,如果原Stream對象已經是並行的就會返回原對象
    unordered 返回一個不關心順序的Stream對象,如果原對象已經是這類型的對象就會返回原對象
    onClose 返回一個相等的Steam對象,同時新的Stream對象在執行Close方法時會調用傳入的Runnable對象
    close 關閉Stream對象
    filter 元素過濾:對Stream對象按指定的Predicate進行過濾,返回的Stream對象中僅包含未被過濾的元素
    map 元素一對一轉換:使用傳入的Function對象對Stream中的所有元素進行處理,返回的Stream對象中的元素爲原元素處理後的
    mapToInt 元素一對一轉換:將原Stream中的使用傳入的IntFunction加工後返回一個IntStream對象
    flatMap 元素一對多轉換:對原Stream中的所有元素進行操作,每個元素會有一個或者多個結果,然後將返回的所有元素組合成一個統一的Stream並返回;
    distinct 去重:返回一個去重後的Stream對象
    sorted 排序:返回排序後的Stream對象
    peek 使用傳入的Consumer對象對所有元素進行消費後,返回一個新的包含所有原來元素的Stream對象
    limit 獲取有限個元素組成新的Stream對象返回
    skip 拋棄前指定個元素後使用剩下的元素組成新的Stream返回
    takeWhile 如果Stream是有序的(Ordered),那麼返回最長命中序列(符合傳入的Predicate的最長命中序列)組成的Stream;如果是無序的,那麼返回的是所有符合傳入的Predicate的元素序列組成的Stream。
    dropWhile 與takeWhile相反,如果是有序的,返回除最長命中序列外的所有元素組成的Stream;如果是無序的,返回所有未命中的元素組成的Stream。
  • 終端操作如表:

    方法 說明
    iterator 返回Stream中所有對象的迭代器;
    spliterator 返回對所有對象進行的spliterator對象
    forEach 對所有元素進行迭代處理,無返回值
    forEachOrdered 按Stream的Encounter所決定的序列進行迭代處理,無返回值
    toArray 返回所有元素的數組
    reduce 使用一個初始化的值,與Stream中的元素一一做傳入的二合運算後返回最終的值。每與一個元素做運算後的結果,再與下一個元素做運算。它不保證會按序列執行整個過程。
    collect 根據傳入參數做相關匯聚計算
    min 返回所有元素中最小值的Optional對象;如果Stream中無任何元素,那麼返回的Optional對象爲Empty
    max 與Min相反
    count 所有元素個數
    anyMatch 只要其中有一個元素滿足傳入的Predicate時返回True,否則返回False
    allMatch 所有元素均滿足傳入的Predicate時返回True,否則False
    noneMatch 所有元素均不滿足傳入的Predicate時返回True,否則False
    findFirst 返回第一個元素的Optioanl對象;如果無元素返回的是空的Optional; 如果Stream是無序的,那麼任何元素都可能被返回。
    findAny 返回任意一個元素的Optional對象,如果無元素返回的是空的Optioanl。

|isParallel| 判斷是否當前Stream對象是並行的|

3.4 對象流的使用–詳解

  • Filter:

    • 概述: 用於對Stream中的元素進行過濾,返回一個過濾後的Stream;
    • 方法定義:
      	Stream<T> filter(Predicate<? super T> predicate);
      
    • 使用示例:
      • 定義Student.java學生類:
      /**
       * @program:
       * @description:
       * @author: anyu
       * @create: 2020-03-09
       */
      @Data
      public class Student implements Serializable {
          private String name;
          private String age;
      }
      
      • 定義測試類:
          public static void main(String[] args) {
          Student student1=new Student();
          student1.setAge("18");
          Student student2 =new Student();
          student2.setAge("19");
      
          List<Student> list=Lists.newArrayList();
          Collections.addAll(list, student1, student2);
      
          long count = list.stream().filter(x -> x.getAge().equals("18")).count();            // 打印出list集合內年齡等於18歲的數據總數
          System.err.println(count);
      }
      

      這裏定義了一個有年齡對象的集合,使用filter能夠過濾出年齡等於18歲的個數。實際上不使用.count,用其他的終結語句還能轉換爲另一個集合,或者找出等於18歲的這個數據的具體對象;

  • map:

    • 概述:它能對stream流內的元素進行轉換,通過映射將元素依次進行轉換處理;
    • 方法定義:
      <R> Stream<R> map(Function<? super T, ? extends R> mapper);
      
    • 使用示例:
          public static void main(String[] args) {
          Student student1=new Student();
          student1.setAge("18");
          Student student2 =new Student();
          student2.setAge("19");
      
          List<Student> list=Lists.newArrayList();
          Collections.addAll(list, student1, student2);
      
          List<String> collect = list.stream().map(x -> x.getAge() + "歲").collect(Collectors.toList());
          System.err.println(JSON.toJSONString(collect));
      }
      

      這裏的 x-> x… 裏面的x可以用其他字母代替,它只是一個替代名稱,不具備實際意義,表示集合內的這一個元素;
      通過map集合,我們可以將每個值進行映射,帶了一個歲的後綴,當使用collect終結stream時,就轉換成了一個新的stream流了。代碼運行後控制檯打印結果如下:
      在這裏插入圖片描述

  • flatMap:

    • 概述: 元素一對多轉換:對原Stream中的所有元素使用傳入的Function進行處理,每個元素經過處理後生成一個多個元素的Stream對象,然後將返回的所有Stream對象中的所有元素組合成一個統一的Stream並返回。
    • 方法定義:
      <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
      
    • 使用示例:
          public static void main(String[] args) {
          Student student1 = new Student();
          student1.setAge("18");
          student1.setName("張三啊");
          Student student2 = new Student();
          student2.setAge("19");
          student2.setName("李四啊");
      
          List<Student> list = Lists.newArrayList();
          Collections.addAll(list, student1, student2);
      
          List<String> collect = list.stream().flatMap(x->Stream.of(x.getName())).collect(Collectors.toList());
          System.err.println(JSON.toJSONString(collect));
      }
      
    > 這裏的flatMap可以將每個元素再Stream流化,能夠進行更細粒度的操作。比起Map的一對一,它的功能更爲強大,具體的區別可百度。
    
  • takeWhile:

    • 概述:taskWhile用於Stream流的過濾操作,它篩選出符合條件的數據。當Stream流爲有序Stream時,當遇到不符合條件的元素時,即進行終止。當Stream流爲無序時,返回符合條件的所有元素;

      它與Filter有點類似,不同之處在於當Stream是有序的,它遇到不符合條件的就會停止,而Filter會繼續篩選。

    • 方法定義:
      default Stream<T> takeWhile(Predicate<? super T> predicate)
      
    • 代碼示例:
      Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
      //以下結果將打印: "test", "t1", "t2", "teeeee",最後的那個taaa不會進行打印 
      s.takeWhile(n -> n.contains("t")).forEach(System.out::println);
      
  • dropWhile:

    • 概述:與takeWhile相反,返回takeWhile得不到的數據。它返回指定範圍外的數據。當Stream是有序的,它遇到不符合條件的就會停止,然後拿取第一個不符合條件的後面的全部數據;
    • 方法定義:
      default Stream<T> dropWhile(Predicate<? super T> predicate)
      
  • forEach

    • 概述:forEach可以說是最常見的操作了,甚至對於List等實現了Collection接口的類可以不創建Stream而直接使用forEach。簡單地說,forEach就是遍歷並執行某個操作。
    • 代碼示例:
          public static void main(String[] args) {
          Student student1 = new Student();
          student1.setAge("18");
          student1.setName("張三啊");
          Student student2 = new Student();
          student2.setAge("19");
          student2.setName("李四啊");
          Student student3=new Student();
          student3.setAge("20");
          student3.setName("王五啊");
      
          List<Student> list = Lists.newArrayList();
          Collections.addAll(list, student1, student2,student3);
      
          list.forEach(x-> System.err.println(x.getAge()));
      }
      

      使用Foreach能夠對集合進行遍歷,並進行一系列操作。集合可以直接使用foreach;控制檯打印結果如下:
      在這裏插入圖片描述

  • sorted和distinct:

    • 概述: sorted表示排序,distinct表示去重;
    • 代碼示例:
      	Integer[] arr = new Integer[]{5, 1, 2, 1, 3, 1, 2, 4};    // 千萬不要用int
      	Stream.of(arr).sorted().forEach(System.out::println);
      	Stream.of(arr).distinct().forEach(System.out::println);
      	Stream.of(arr).distinct().sorted().forEach(System.out::println);
      
  • collect:

    • 概述:在流操作中,我們往往需求是從一個List得到另一個List,而不是直接通過forEach來打印。那麼這個時候就需要使用到collect了。
    • 示例如下:
          public static void main(String[] args) {
          Student student1 = new Student();
          student1.setAge("18");
          student1.setName("張三啊");
          Student student2 = new Student();
          student2.setAge("19");
          student2.setName("李四啊");
          Student student3=new Student();
          student3.setAge("20");
          student3.setName("王五啊");
      
          List<Student> list = Lists.newArrayList();
          Collections.addAll(list, student1, student2,student3);
      
          List<Student> collect = list.stream().filter(x -> x.getAge().equals("19")).collect(Collectors.toList());
          System.err.println(JSON.toJSONString(collect));
      }
      

      通過過濾出年齡等於19歲的元素,然後轉爲新的list,打印時就可以得到新的list集合了。打印結果如圖所示:在這裏插入圖片描述

3.5 補充

  • 並行流:除了普通的stream之外還有parallelStream,區別比較直觀,就是stream是單線程執行,parallelStream爲多線程執行。parallelStream的創建及使用基本與Stream類似。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章