1. 程式人生 > >Java8 新特性 Lambda & Stream API

Java8 新特性 Lambda & Stream API

目錄
  • Lambda & Stream API
    • 1 Lambda表示式
      • 1.1 為什麼要使用lambda表示式
      • 1.2 Lambda表示式語法
      • 1.3 函式式介面
        • 1.3.1 什麼是函式式介面?
        • 1.3.2 如何理解函式式介面
        • 1.3.3 定義函式式介面
        • 1.3.4 函式式介面作為引數傳遞
      • 1.4 方法引用與構造器引用
        • 1.4.1 方法引用
        • 1.4.2 構造器引用
        • 1.4.3 陣列引用
    • 2 Stream API
      • 2.1 Stream API說明
      • 2.2 為什麼要使用Stream API
      • 2.3 什麼是Stream
      • 2.4 Stream 操作的三個步驟
        • 2.4.1 建立Stream
        • 2.4.2 Stream 中間操作
          • 2.4.2.1 篩選與切片
          • 2.4.2.2 對映
          • 2.4.2.3 排序
        • 2.4.3 Stream 的終止操作
          • 2.4.3.1 匹配與查詢
          • 2.4.3.2 規約
          • 2.4.3.3 收集

Lambda & Stream API

lambda和stream Api 都是Java8的新特性 首先 簡單介紹一下java8

Java8 (jdk 1.8) 是Java語言開發的一個主要版本

Java8 是Oracle 公司於2014年3月釋出,可以看成是自Java5以來最具革命性的版本。

Java8為Java語言、編譯器、類庫、開發工具與JVM帶來了大量新特性。

簡介:

  • 速度更快
  • 程式碼更少 :增加新的語法 lambda表示式
  • 強大的 Stream API
  • 便於並行
  • 最大化減少空指標異常 Optional
  • Nashorn引擎,允許在JVM上執行JS應用

1 Lambda表示式

1.1 為什麼要使用lambda表示式

​ Lambda是一個匿名函式,我們可以把lambda表示式理解為是 一段可以傳遞的程式碼,即程式碼像資料一樣進行傳遞。使用它可以寫出更簡潔、更靈活的程式碼。作為一種更緊湊的程式碼風格。

先來看一個簡單案例:

需求:開啟一個執行緒,在控制檯輸出 hello sky

下面分別使用三種方法實現

  1. 實現runnable介面
  • 先定義一個類實現Runnable介面
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello sky !!!");
    }
}
  • 呼叫
//方式一 :實現runable介面
MyRunnable myRunable = new MyRunnable();
Thread t1 = new Thread(myRunable);
t1.start();
  1. 匿名內部類
//方式二 :匿名內部類
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello sky !!!");
}
}).start();
  1. lambda表示式
//方式三:lambda
new Thread(() -> System.out.println("Hello sky !!!")).start();

1.2 Lambda表示式語法

​ 在Java8語言中引入了一種新的語法元素和操作符 “->” ,該操作符被稱為 Lambda操作符 或 箭頭操作符 。它將Lambda分為兩個部分

左側:指定了Lambda表示式需要的引數列表

右側:指定了Lambda體,是抽象方法的實現邏輯,也即是Lambda表示式要執行的功能。

//語法格式一:無參,無返回值
Runnable r = () -> {
    System.out.println("Hello Lambda !");
};

//語法格式二:一個引數 沒有返回值
Consumer<String> con1 = (String str) -> {
    System.out.println(str);
};

//語法格式三:資料型別可以省略,可由編譯器推斷得出,稱為 “型別推斷”
Consumer<String> con2 = (str) -> {
    System.out.println(str);
};

//語法格式四:若只有一個引數,引數的小括號可以省略
Consumer<String> con3 = str -> {
    System.out.println(str);
};

//語法格式五:多個引數 並有返回值
Comparator<Integer> com = (x,y) -> {
    System.out.println("兩個引數,有返回值");
    return Integer.compare(x,y);
};

//語法格式六:當只有一條語句時,return和{} 都可以省略
Comparator<Integer> com2 = (x,y) -> Integer.compare(x,y);

型別推斷:

​ 上述Lambda表示式中的引數型別都是由編譯器推斷得出的。Lambda表示式中無需指定型別,程式依然可以編譯,這是因為javac根據程式的上下文,在後臺推斷得出了引數的型別,Lambda表示式的型別推斷依賴於上下文環境,如上述語法格式三,就是根據Consumer中指定的泛型,可推斷出引數型別為String.

1.3 函式式介面

1.3.1 什麼是函式式介面?

  • 只包含一個抽象方法的介面,稱之為 函式式介面
  • 你可以通過 Lambda 表示式來建立該介面的物件。(若 Lambda 表示式 丟擲一個受檢異常(即:非執行時異常),那麼該異常需要在目標介面的抽 象方法上進行宣告)。
  • 我們可以在一個介面上使用@FuntionalInterface註解,這樣就可以檢查它是否是一個函式式介面。
  • 在java.util.function包下定義了Java8的豐富的函式式介面

1.3.2 如何理解函式式介面

  • Java從誕生之日起就是一直倡導“一切皆物件”,在Java裡面,面向物件(OOP)是一切。但是隨著python、Scala等語言的興起和新技術的挑戰,java不得不做出調整以便支援更加廣泛的技術要求,也即java不但可以支援OOP還可以支援OOF(面向函式程式設計)
  • 在函數語言程式設計語言當中,函式被當做一等公民對待。在將函式作為一等公民的 程式語言中,Lambda表示式的型別是函式。但是在Java8中,有所不同。在 Java8中,Lambda表示式是物件,而不是函式,它們必須依附於一類特別的 物件型別——函式式介面。
  • 簡單來說,在Java8中,Lambda表示式就是一個函式式介面的例項。這就是Lambda表示式和函式式介面的關係。也就是說,只要一個物件是函式式介面的例項,那麼該物件就可以用Lambda表示式類表示
  • 所有以前用匿名函式實現類表示的現在都可以用Lambda表示式來寫

1.3.3 定義函式式介面

  1. 先看一個原始碼中的案例,用上面我們用到的Runnable為例
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
  1. 自定義函式式介面
@FunctionalInterface
public interface MyInterface {
    int add(int a, int b);
}

注意:

@FunctionalInterface 註解的作用只是檢查這個介面是否為 函式式介面,並不是一定要加上這個註解

在idea中,如果介面不符合函式式介面的規範,編輯器會直接報錯

在Java8中,介面中方法可以有預設實現,通過default關鍵字修飾的方法 就不是一個必須被實現的抽象方法,這種介面也是符合函式式介面規範的

/**
 * @Author sky
 * @Site cmtianxie163.com 2020/4/10 16:42
 */
@FunctionalInterface
public interface MyInterface {

    int add(int a, int b);

    default void test1(){}
}

1.3.4 函式式介面作為引數傳遞

/**
 * @Author sky
 * @Site cmtianxie163.com 2020/4/10 22:00
 */
public class LambdaTest4 {
    public static void main(String[] args) {
        happyMoney(100, m -> System.out.println("今天花了"+m));

        List<String> list = Arrays.asList("北京", "上海", "南京", "六安", "合肥", "東京");
        List<String> list1 = filterString2(list, s -> s.contains("京"));
        System.out.println(list1);
    }

    static void happyMoney(double money, Consumer<Double> con){
        con.accept(money);
    }

    static List<String> filterString(List<String> list, Predicate<String> pre){
        List<String> newlist = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)){
                newlist.add(s);
            }
        }
        return newlist;
    }

    static List<String> filterString2(List<String> list, Predicate<String> pre){
        List<String> newlist = new ArrayList<>();
        list.forEach(s -> {if (pre.test(s)){
            newlist.add(s);
        }});
        return newlist;
    }
}

Java 四大內建核心函式式介面

函式式介面 引數型別 返回值型別 用途
Consumer T void 對型別為T的物件應用操作,包含方法: void accept(T t)
Supplier T 返回型別為T的物件,包含方法:T get()
Function<T,R>
函式型介面
T R 對型別為T的物件應用操作,並返回結果。結 果是R型別的物件。包含方法:R apply(T t)
Predicate T boolean 確定型別為T的物件是否滿足某約束,並返回 boolean 值。包含方法:boolean test(T t)

其他介面

函式式介面 引數型別 返回型別 用途
BiFunction<T,U,R> T,U R 對型別為 T, U 引數應用操作,返回 R 型別的結 果。包含方法為: R apply(T t, U u);
UnaryOperator T T 對型別為T的物件進行一元運算,並返回T型別的 結果。包含方法為:T apply(T t);
BinaryOperator T,T T 對型別為T的物件進行二元運算,並返回T型別的 結果。包含方法為: T apply(T t1, T t2);
BiConsumer<T,U> T,U void 對型別為T, U 引數應用操作。 包含方法為: void accept(T t, U u)
BiPredicate<T,U> T,U boolean 包含方法為: boolean test(T t,U u)
ToIntFunction T int
long
double
分別計算int、long、double值的函式
IntFunction int
long
double
R 引數分別為int、long、double 型別的函式

1.4 方法引用與構造器引用

1.4.1 方法引用

  • 當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用
  • 方法引用可以看做是Lambda表示式深層次的表達。換句話說,方法引用就 是Lambda表示式,也就是函式式介面的一個例項,通過方法的名字來指向 一個方法,可以認為是Lambda表示式的一個語法糖。
  • 要求:實現介面的抽象方法的引數列表和返回值型別,必須與方法引用的 方法的引數列表返回值型別保持一致!
  • 格式:使用操作符 “::” 將類(或物件) 與 方法名分隔開來。
  • 如下三種主要使用情況:
    1. 物件::例項方法名
    2. 類::靜態方法名
    3. 類::例項方法名

先定義一個Employee類和EmployeeData類(提供假資料)

package org.itsky.study.test2;
import com.sun.org.apache.xpath.internal.operations.Equals;
import java.util.Objects;

public class Employee {

	private int id;
	private String name;
	private int age;
	private double salary;

	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 double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	public Employee() {
		System.out.println("Employee().....");
	}

	public Employee(int id) {
		this.id = id;
		System.out.println("Employee(int id).....");
	}

	public Employee(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public Employee(int id, String name, int age, double salary) {

		this.id = id;
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	@Override
	public String toString() {
		return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
	}

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;

		Employee employee = (Employee) o;

		if (id != employee.id)
			return false;
		if (age != employee.age)
			return false;
		if (Double.compare(employee.salary, salary) != 0)
			return false;
		return name != null ? name.equals(employee.name) : employee.name == null;
	}




	@Override
	public int hashCode() {
		int result;
		long temp;
		result = id;
		result = 31 * result + (name != null ? name.hashCode() : 0);
		result = 31 * result + age;
		temp = Double.doubleToLongBits(salary);
		result = 31 * result + (int) (temp ^ (temp >>> 32));
		return result;
	}
}

public class EmployeeData {
	
	public static List<Employee> getEmployees(){
		List<Employee> list = new ArrayList<>();
		
		list.add(new Employee(1001, "魯班七號", 34, 6000.38));
		list.add(new Employee(1002, "黃忠", 12, 9876.12));
		list.add(new Employee(1003, "孫尚香", 33, 3000.82));
		list.add(new Employee(1004, "后羿", 26, 7657.37));
		list.add(new Employee(1005, "成吉思汗", 65, 5555.32));
		list.add(new Employee(1006, "狄仁傑", 42, 9500.43));
		list.add(new Employee(1007, "伽羅", 26, 4333.32));
		list.add(new Employee(1008, "馬可波羅", 35, 2500.32));
        list.add(new Employee(1008, "馬可波羅", 35, 2500.32));
		
		return list;
	}
	
}

方法引用測試程式碼:

// 情況一:物件 :: 例項方法
	//Consumer中的void accept(T t)
	//PrintStream中的void println(T t)
	public static void test1() {
		Consumer<String> con1 = str -> System.out.println(str);
		con1.accept("北京");

		System.out.println("*******************");
		PrintStream ps = System.out;
		Consumer<String> con2 = ps::println;
		con2.accept("beijing");
	}
	
	//Supplier中的T get()
	//Employee中的String getName()
	public static void test2() {
		Employee emp = new Employee(1001,"Tom",23,5600);

		Supplier<String> sup1 = () -> emp.getName();
		System.out.println(sup1.get());

		System.out.println("*******************");
		Supplier<String> sup2 = emp::getName;
		System.out.println(sup2.get());

	}

	// 情況二:類 :: 靜態方法
	//Comparator中的int compare(T t1,T t2)
	//Integer中的int compare(T t1,T t2)
	public static void test3() {
		Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
		System.out.println(com1.compare(12,21));

		System.out.println("*******************");

		Comparator<Integer> com2 = Integer::compare;
		System.out.println(com2.compare(12,3));

	}
	
	//Function中的R apply(T t)
	//Math中的Long round(Double d)
	public static void test4() {
		Function<Double,Long> func = new Function<Double, Long>() {
			@Override
			public Long apply(Double d) {
				return Math.round(d);
			}
		};

		System.out.println("*******************");

		Function<Double,Long> func1 = d -> Math.round(d);
		System.out.println(func1.apply(12.3));

		System.out.println("*******************");

		Function<Double,Long> func2 = Math::round;
		System.out.println(func2.apply(12.6));
	}

	// 情況三:類 :: 例項方法
	// Comparator中的int comapre(T t1,T t2)
	// String中的int t1.compareTo(t2)
	public static void test5() {
		Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
		System.out.println(com1.compare("abc","abd"));

		System.out.println("*******************");

		Comparator<String> com2 = String :: compareTo;
		System.out.println(com2.compare("abd","abm"));
	}

	//BiPredicate中的boolean test(T t1, T t2);
	//String中的boolean t1.equals(t2)
	public static void test6() {
		BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
		System.out.println(pre1.test("abc","abc"));

		System.out.println("*******************");
		BiPredicate<String,String> pre2 = String :: equals;
		System.out.println(pre2.test("abc","abd"));
	}
	
	// Function中的R apply(T t)
	// Employee中的String getName();
	public static void test7() {
		Employee employee = new Employee(1001, "Jerry", 23, 6000);


		Function<Employee,String> func1 = e -> e.getName();
		System.out.println(func1.apply(employee));

		System.out.println("*******************");


		Function<Employee,String> func2 = Employee::getName;
		System.out.println(func2.apply(employee));
	}

1.4.2 構造器引用

格式: ClassName::new

與函式式介面相結合,自動與函式式介面中方法相容。 可以把構造器引用賦值給定義的方法,要求構造器引數列表要與介面中抽象 方法的引數列表一致!且方法的返回值即為構造器對應類的物件。

例如:

Function<Integer,MyClass> fun = (n) -> new MyClass(n);

等同於:

Function<Integer,MyClass> fun = MyClass::new;

1.4.3 陣列引用

格式:type[]::new

例如:

Function<Integer,Integer[]> fun = (n) -> new Integer[n];

等同於:

Function<Integer,Integer[]> fun = (n) -> Integer[]::new;

//構造器引用
    //Supplier中的T get()
    //Employee的空參構造器:Employee()

    public static void test1(){

        Supplier<Employee> sup = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println("*******************");

        Supplier<Employee>  sup1 = () -> new Employee();
        System.out.println(sup1.get());

        System.out.println("*******************");

        Supplier<Employee>  sup2 = Employee :: new;
        System.out.println(sup2.get());
    }

    //Function中的R apply(T t)

    public static void test2(){
        Function<Integer,Employee> func1 = id -> new Employee(id);
        Employee employee = func1.apply(1001);
        System.out.println(employee);

        System.out.println("*******************");

        Function<Integer,Employee> func2 = Employee :: new;
        Employee employee1 = func2.apply(1002);
        System.out.println(employee1);

    }

    //BiFunction中的R apply(T t,U u)

    public static void test3(){
        BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
        System.out.println(func1.apply(1001,"Tom"));

        System.out.println("*******************");

        BiFunction<Integer,String,Employee> func2 = Employee :: new;
        System.out.println(func2.apply(1002,"Tom"));

    }

    //陣列引用
    //Function中的R apply(T t)

    public static void test4(){
        Function<Integer,String[]> func1 = length -> new String[length];
        String[] arr1 = func1.apply(5);
        System.out.println(Arrays.toString(arr1));

        System.out.println("*******************");

        Function<Integer,String[]> func2 = String[] :: new;
        String[] arr2 = func2.apply(10);
        System.out.println(Arrays.toString(arr2));

    }

2 Stream API

2.1 Stream API說明

  • Java8中有兩大最為重要的改變。第一個是 Lambda 表示式;另外一個則 是 Stream API
  • Stream API(java.util.stream)把真正的函數語言程式設計風格引入到java中。這 是目前為止對Java類庫最好的補充,因為Stream API可以極大提供Java程 序員的生產力,讓程式設計師寫出高效率、乾淨、簡潔的程式碼。
  • Stream API 是Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾、和對映資料等操作。使用Stream API對集合進行操作,就類似於使用SQL執行的資料庫查詢。也可以使用 Stream API 來並行執行操作。簡言之,Stream API 提供了一種 高效且易於使用的處理資料的方式。

2.2 為什麼要使用Stream API

  • 實際開發中,專案中多數資料來源都來自於Mysql,Oracle等。但現在資料來源可以更多了,有MongoDB,Redis等,而這些NoSQL的資料就需要Java層面去處理了。
  • Stream 和 Collection 的區別:
    • Collection 是一種靜態的記憶體資料結構,而Stream 是有關計算的
    • 前者主要面向記憶體,儲存在記憶體中,後者主要面向CPU,通過CPU計算實現。

2.3 什麼是Stream

是資料渠道,用於操作資料來源(集合、陣列等)所生成的元素序列。

集合講的是資料,Stream講的是計算

  • Stream 自己不會儲存元素。
  • Stream 自己不會改變源物件。相反,他們會返回一個持有結果的新Stream。
  • Stream 操作時延遲執行的。這意味著他們會等到需要結果的時候才執行。

2.4 Stream 操作的三個步驟

  1. 建立Stream
  2. 中間操作
  3. 終止操作

一旦執行終止操作,就執行中間操作鏈,併產生結果。之後將不會再被使用

2.4.1 建立Stream

  1. 通過集合建立

    • default Stream
    • default Stream
    /**
     * @Author sky
     * @Site cmtianxie163.com 2020/4/13 10:24
     */
    public class StreamAPITest1 {
        public static void main(String[] args) {
            List<String> list = Arrays.asList("java","python","go");
    
            Stream<String> stream = list.stream();
            Stream<String> parallelStream = list.parallelStream();
    
            stream.forEach(s -> System.out.println(s));
            System.out.println("----------------------");
            parallelStream.forEach(s -> System.out.println(s));
        }
    }
    
  2. 通過陣列建立

    Java8中的Arrays的靜態方法 stream() 可以獲取陣列流

    /**
         * Returns a sequential {@link Stream} with the specified array as its
         * source.
         *
         * @param <T> The type of the array elements
         * @param array The array, assumed to be unmodified during use
         * @return a {@code Stream} for the array
         * @since 1.8
         */
        public static <T> Stream<T> stream(T[] array) {
            return stream(array, 0, array.length);
        }
    

    過載形式,能夠處理對應基本型別的陣列:

    • public static IntStream stream(int[] array)
    • public static LongStream stream(long[] array)
    • public static DoubleStream stream(double[] array)
    int[] array1 = new int[]{1,2,3,4,5};
    IntStream intStream = Arrays.stream(array1);
    
    double[] array2 = new double[]{11,22,33,44};
    DoubleStream doubleStream = Arrays.stream(array2);
    
    intStream.forEach(s -> System.out.println(s));
    doubleStream.forEach(s -> System.out.prinln(s));
    
  3. 通過stream的 of()

可以呼叫Stream類靜態方法of() ,通過顯示值建立一個流,它可以接收任意數量的引數。

  • public static
Stream<Object> objectStream = Stream.of("1", 1, 1.0, intStream);
objectStream.forEach(s -> System.out.println(s));
  1. 建立無限流

可以使用靜態方法Stream.iterate() 和 Stream.generate(),建立無限流。

  • 迭代

    public static Stream iterate(final T seed, final UnaryOperator f)

  • 生成

    public static Stream generate(Supplier s)

//建立無限流
//從10開始 遍歷前十個偶數
Stream<Integer> iterateStream = Stream.iterate(0, t -> t + 2).limit(10);
iterateStream.forEach(s -> System.out.println(s));

//生成
//生成十個隨機數
Stream<Double> generateStream = Stream.generate(Math::random).limit(10);
generateStream.forEach(System.out::println);

2.4.2 Stream 中間操作

多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理,而在終止操作執行時一次性全部處理,稱為 惰性求值

2.4.2.1 篩選與切片
方法 描述
filter(Predicate p) 接收 Lambda , 從流中排除某些元素
distinct() 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素
limit(long maxSize) 截斷流,使其元素不超過給定數量
skip(long n) 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一 個空流。與 limit(n) 互補
List<Employee> list = EmployeeData.getEmployees();
//練習:查詢員工表中薪資大於7000的員工資訊
list.stream().filter(employee -> employee.getSalary()>7000).forEach(System.out::println);
System.out.println("-------------------");
//截斷流,使其元素不超過給定數量
list.stream().limit(3).forEach(System.out::println);
System.out.println("-------------------");
//跳過元素,返回一個扔掉了前 n 個元素的流
list.stream().skip(3).forEach(System.out::println);
System.out.println("-------------------");
//篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素
list.stream().distinct().forEach(System.out::println);
2.4.2.2 對映
方法 描述
map(Function f) 接收一個函式作為引數,該函式會被應用到每個元 素上,並將其對映成一個新的元素。
mapToDouble(ToDoubleFunction f) 接收一個函式作為引數,該函式會被應用到每個元 素上,產生一個新的 DoubleStream。
mapToInt(ToTIntFunction f) 接收一個函式作為引數,該函式會被應用到每個元 素上,產生一個新的 IntStream。
mapToLong(ToLongFunction f) 接收一個函式作為引數,該函式會被應用到每個元 素上,產生一個新的 LongStream。
flatMap(Function f) 接收一個函式作為引數,將流中每個值都換成另一個流,然後把所有流連線成一個流
List<String> list1 = Arrays.asList("aa", "bb", "cc", "dd", "ee");
list1.stream().skip(1).map(str -> str.toUpperCase()).forEach(System.out::println);
System.out.println("-------------------");
//獲取員工姓名長度大於3的員工的姓名
list.stream().map(Employee::getName).filter(name -> name.length()>3).forEach(System.out::println);

Stream<Stream<Character>> streamStream = list1.stream().map(StreamAPITest2::fromStringToStream);
streamStream.forEach(System.out::println);
System.out.println("-------------------");
//flatMap
Stream<Character> characterStream = list1.stream().flatMap(StreamAPITest2::fromStringToStream);
characterStream.forEach(System.out::println);
//將字串中的多個字元構成的集合轉換為對應的Stream例項
public static Stream<Character> fromStringToStream(String str){
    ArrayList<Character> list = new ArrayList<>();
    for (Character c : str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}

flatMap 有個類似的例子

如 list集合 如果想新增一個元素 這個元素本身也是集合

  1. 元素就是集合
  2. 相當於集合(元素)先遍歷出來 再一個個新增到集合中
ArrayList list1 = new ArrayList();
list1.add(1);
list1.add(2);
list1.add(3);

ArrayList list2 = new ArrayList();
list2.add(4);
list2.add(5);
list2.add(6);
//集合長度加一
//list1.add(list2);
//集合長度加三
list1.addAll(list2);
2.4.2.3 排序
方法 描述
sorted 產生一個新流,其中按自然順序排序
sorted(Comparator c) 產生一個新流,其中按比較器順序排序
//自然排序
List<Integer> list2 = Arrays.asList(1,4,7,3,2,8,111,4);
list2.stream().sorted().forEach(System.out::println);
//定製排序
//安裝年齡排序 年齡相同的再安裝薪資排序
list.stream().sorted(((o1, o2) -> {
    int compare = Integer.compare(o1.getAge(), o2.getAge());
    if(compare == 0){
        return Double.compare(o1.getSalary(),o2.getSalary());
    }else{
        return compare;
    }
})).forEach(System.out::println);

2.4.3 Stream 的終止操作

  • 終止操作會從流的流水線生成結果。其結果可以是任何不是流的值,如:List、Integer、void
  • 流進行終止操作後,不能再次使用
2.4.3.1 匹配與查詢
方法 描述
allMatch(Predicate p) 檢查是否匹配所有元素
anyMatch(Predicate p) 檢查是否至少匹配一個元素
noneMatch(Predicate p) 檢查是否沒有匹配所有元素
findFirst() 返回第一個元素
findAny() 返回當前流中的任意元素
count() 返回流中元素總數
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 內部迭代(使用 Collection 介面需要使用者去做迭代, 稱為外部迭代。相反,Stream API 使用內部迭 代——它幫你把迭代做了)
List<Employee> employees = EmployeeData.getEmployees();

//是否所有員工年齡都大於18
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println(allMatch);
//是否存在員工姓 孫
boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("孫"));
System.out.println(noneMatch);
//返回第一個元素
Optional<Employee> first = employees.stream().findFirst();
Employee employee = first.get();
System.out.println(employee);
//返回當前流中的任意元素
Employee employee1 = employees.parallelStream().findAny().get();
System.out.println(employee1);

//返回流中元素總個數
long count = employees.stream().count();
System.out.println(count);
//返回最高工資
Stream<Double> doubleStream = employees.stream().map(e -> e.getSalary());
Double maxSalary = doubleStream.max(Double::compare).get();
System.out.println(maxSalary);
//返回最低工資的員工
Employee minSalaryEmp = employees.stream().min((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary())).get();
System.out.println(minSalaryEmp);
//內部迭代
employees.stream().forEach(System.out::println);
//集合遍歷
employees.forEach(System.out::println);
2.4.3.2 規約
方法 描述
reduce(T iden, BInaryOperator b) 可以將流中的元素反覆結合起來,得到一個值。返回T
reduce(BinaryOperator b) 可以將流中元素反覆結合起來,得到一 個值。返回 Optional

map 和 reduce 的連線通常稱為 map-reduce 模式,因 Google 用它來進行網路搜尋而出名。

//計算 1-10 的自然數之和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);
//計算公司所有員工工資的總和
Optional<Double> sumSalary = employees.stream().map(e -> e.getSalary()).reduce((s1, s2) -> s1 + s2);
System.out.println(sumSalary.get());
2.4.3.3 收集
方法 描述
collect(Collector c) 將流轉換為其他形式。接收一個Collector介面的實現,用於給Stream中元素做彙總的方法
//練習1:查詢工資大於6000的員工,結果返回為一個List或Set

List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());

employeeList.forEach(System.out::println);
System.out.println();
Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());

employeeSet.forEach(System.out::println);

Collector 介面中方法的實現決定了如何對流執行收集的操作(如收集到 List、Set、 Map)。 另外, Collectors 實用類提供了很多靜態方法,可以方便地建立常見收集器例項, 具體方法與例項如下表:

方法 返回型別 作用
toList List 把流中元素收集到List
toSet Set 把流中元素收集到Set
toCollection Collection 把流中元素收集到建立的集合
counting Long 計算流中元素的個數
summingInt Integer 對流中元素的整數屬性求和
averagingInt Double 計算流中元素Integer屬性的平均值
summarizingInt IntSummaryStatistics 收集流中Integer屬性的統計值。如:平 均值
joining String 連線流中每個字串
maxBy Optional 根據比較器選擇最大值
minBy Optional 根據比較器選擇最小值
reducing 歸約產生的型別 從一個作為累加器的初始值開始, 利用BinaryOperator與流中元素逐 個結合,從而歸約成單個值
collectingAndThen 轉換函式返回的型別 包裹另一個收集器,對其結果轉 換函式
groupingBy Map<K,List 根據某屬性值對流分組,屬性為K, 結果為V
partitioningBy Map<Boolean,List 根據true或false進行分割槽
List<Employee> emps= list.stream().collect(Collectors.toList());
Set<Employee> emps= list.stream().collect(Collectors.toSet());
Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
long count = list.stream().collect(Collectors.counting());
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
Map<Emp.Status, List<Emp>> map= list.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));