1. 程式人生 > >2020你還不會Java8新特性?(學習過程記錄)

2020你還不會Java8新特性?(學習過程記錄)

Java8(1)新特性介紹及Lambda表示式

前言:

跟大娃一塊看,把原來的電腦拿出來放中間看視訊用

--- 以後會有的課程 難度

  1. 深入Java 8 難度1
  2. 併發與netty 難度3
  3. JVM 難度4
  4. node 難度2
  5. spring精髓 難度1

課程中提到的知識:

前後端分離的開發,是靠node當做中間的

netty,已經成為國內外網際網路公司的標配。會涉及底層的原始碼的理解。

JVM 涉及的東西比較多。雖然天天用,但是沒有深入理解過。各種鎖,可見性等。與計算機原理息息相關的。

聖思園主要面對與已經工作的。大部分為一線的開發人員。
課程一定是完整的。由淺入深的。一定要有一種耐心。
對於基礎不好的,可以看看以前面授的時候錄製的視訊。不懂的一定要多查資料。

在講課過程中的設計思路:4000塊錢的收費標準。

jdk8

介紹:Java 8可謂Java語言歷史上變化最大的一個版本,其承諾要調整Java程式設計向著函式式風格邁進,這有助於編寫出更為簡潔、表達力更強,並且在很多情況下能夠利用並行硬體的程式碼。本門課程將會深入介紹Java 8新特性,學員將會通過本門課程的學習深入掌握Java 8新增特性並能靈活運用在專案中。學習者將學習到如何通過Lambda表示式使用一行程式碼編寫Java函式,如何通過這種功能使用新的Stream API進行程式設計,如何將冗長的集合處理程式碼壓縮為簡單且可讀性更好的流程式。學習建立和消費流的機制,分析其效能,能夠判斷何時應該呼叫API的並行執行特性。

課程的介紹:

  1. Java 8新特性介紹
  2. Lambda表示式介紹
  3. 使用Lambda表示式代替匿名內部類
  4. Lambda表示式的作用
  5. 外部迭代與內部迭代
  6. Java Lambda表示式語法詳解
  7. 函式式介面詳解
  8. 傳遞值與傳遞行為
  9. Stream深度解析
  10. Stream API詳解
  11. 序列流與並行流
  12. Stream構成
  13. Stream源生成方式
  14. Stream操作型別
  15. Stream轉換
  16. Optional詳解
  17. 預設方法詳解
  18. 方法與構造方法引用
  19. Predicate介面詳解
  20. Function介面詳解
  21. Consumer介面剖析
  22. Filter介紹
  23. Map-Reduce講解、中間操作與終止操作
  24. 新的Date API分析

拉姆達表示式: 函數語言程式設計。以前的叫做命令式的程式設計。

使用面嚮物件語言就是來操作資料,封裝繼承多型。
函數語言程式設計面向的是行為。好處:程式碼可讀性提高。

開發安卓的時候大量的匿名內部類。

提到的關鍵字:
kotlin ,JetBrains 。construction 構造

他以前在學習的時候,翻程式碼。

將要講解的各個技術的簡介、

課程講解的時候遇到的工具:
Mac , jdk8 ,idea(很多功能是通過外掛的形式來實現的)


Java8課程開始

lambda表示式

為什麼要使用lambda表示式

  • 在Java中無法將函式座位引數傳遞給一個方法,也無法返回一個函式的方法。
  • 在js中,函式的引數是一個函式。返回值是另一個函式的情況是非常常見的。是一門經典的函式式語言。

Java匿名內部類。

匿名內部類的介紹

Gradle的使用。可以完全使用maven的中央倉庫。
進行安卓的開發時,gradle已經成為標配了。

lambda:
匿名內部類

 my_jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Button Pressed! ");
            }
        });

改造後

        my_jButton.addActionListener(e -> System.out.println("Button Pressed!"));

lambda表示式的基本結構:

(param1,param2,param3) ->{
    
}

函數語言程式設計: 一個接口裡邊只有一個抽象方法。
可以通過lambda表示式來例項。

關於函式式介面:

  • 如果一個藉口只有一個抽象方法,那麼該介面就是一個函式式介面。
  • 如果我們在某一個介面上聲明瞭functionalInterface註解,那麼編譯器就會按照函式是藉口的定義來要求改介面。
  • 如果某個介面只有一個抽象方法,但是我們並沒有給介面宣告functionnaleInterface註解,編譯器依舊會給改介面看作是函式式介面。

通過例項對函式式介面的理解:

package com.erwa.jdk8;

@FunctionalInterface
interface MyInterface {

    void test();

//    Multiple non-overriding abstract methods found in interface com.erwa.jdk8.MyInterface
//    void te();

    //如果一個介面宣告一個抽象方法,但是這個方法重寫了 object類中的一個方法.
    //介面的抽象方法不會加一.所以依然是函式方法.
    // Object 類是所有類的父類.
    @Override
    String toString();
}

public class Test2 {

    public void myTest(MyInterface myInterface) {
        System.out.println(1);
        myInterface.test();
        System.out.println(2);
    }

    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.myTest(() -> {
            System.out.println(3);
        });
    }

}

接口裡邊從1.8開始也可以有方法實現了。default

    預設方法。
   default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
 
 * <p>Note that instances of functional interfaces can be created with
 * lambda expressions, method references, or constructor reference

lambda表示式的作用:

  • lambda表示式為Java添加了確實的函數語言程式設計特性,使我們能將函式當做一等公民看待。
  • 在將函式座位一等公民的語言中,lambda表示式的型別是函式。但是在Java中,lambda表示式是物件,他們必須依附於一類特別的物件型別-函式式介面(function interface)

迭代的方式:

  • 外部迭代:
  • 內部迭代:
  • 方法引用:
list.forEach(System.out::println);

介面中可以有預設方法和靜態方法。

流: stream

 /**
     * Returns a sequential {@code Stream} with this collection as its source.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a sequential {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a sequential {@code Stream} over the elements in this collection
     * @since 1.8
     */
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

關於流方式實現的舉例:

public static void main(String[] args) {
        //函式式介面的實現方式
        MyInterface1 i1 = () -> {};
        System.out.println(i1.getClass().getInterfaces()[0]);
        MyInterface2 i2 = () -> {};
        System.out.println(i2.getClass().getInterfaces()[0]);

        // 沒有上下文物件,一定會報錯的.
//        () -> {};

        //通過lambda來實現一個執行緒.
        new Thread(() -> System.out.println("hello world")).start();

        //有一個list  ,將內容中的首字母變大寫輸出.
        List<String> list = Arrays.asList("hello","world","hello world");
        //通過lambda來實現所有字母程式設計大寫輸出.
//        list.forEach(item -> System.out.println(item.toUpperCase()));
        //把三個單詞放入到新的集合裡邊.
        List<String> list1 = new ArrayList<>();  //diamond語法. 後邊的<>不用再放型別
//        list.forEach(item -> list1.add(item.toUpperCase()));
//        list1.forEach(System.out::println);

        //進一步的改進. 流的方式
//        list.stream();//單執行緒
//        list.parallelStream(); //多執行緒
        list.stream().map(item -> item.toUpperCase()).forEach(System.out::println);//單執行緒
        list.stream().map(String::toUpperCase).forEach(System.out::println);

        //上邊的兩種方法,都滿足函式式介面的方式.
    }

lambda表示式的作

  • 傳遞行為,而不僅僅是值
    • 提升抽象層次
    • API重用性更好
    • 更加靈活

lambda基本語法

  • (argument) -> (body)
  • 如: (arg1,arg2...) -> (body)

Java lambda結構

  • 一個Lambda表示式可以有0個或者多個引數
  • 引數的型別既可以明確宣告,也可以根據上下文來推斷。例如:(int a) 與 (a) 效果相同
  • 所有引數包含在圓括號內,引數之間用逗號相隔。
  • 空圓括號代表引數集為空。
  • 當只有一個引數,且型別可推倒時。圓括號()可省略。
  • lambda表示式的主體可以包含0條或多條語句。
  • 如果lambda表示式的主體只有一條語句,花括號{}可以省略,匿名函式的返回型別與該主體表達式一致。
  • 如果lambda表示式的主體包含一條以上語句,則表示式必須包含在花括號中。匿名函式的韓繪製型別與程式碼塊的返回型別一致,諾沒有反回則為空。

高階函式:
如果一個函式接收一個函式作為引數,或者返回一個函式作為返回值,那麼該函式就叫做高階函式.

傳遞行為的舉例:

  public static void main(String[] args) {
        // 函式的測試
        // 傳遞行為的一種方式.
        FunctionTest functionTest = new FunctionTest();
        int compute = functionTest.compute(1, value -> 2 * value);

        System.out.println(compute);
        System.out.println(functionTest.compute(2,value -> 5+ value));
        System.out.println(functionTest.compute(3,a -> a * a));

        System.out.println(functionTest.convert(5, a -> a + "hello "));

        /**
         * 高階函式:
         * 如果一個函式接收一個函式作為引數,或者返回一個函式作為返回值,那麼該函式就叫做高階函式.
         */

    }

    //使用lambda表示式的話,可以直覺預定義行為.用的時候傳遞.
    // 即 函數語言程式設計.
    public int compute(int a, Function<Integer, Integer> function) {
        return function.apply(a);
    }

    public String convert(int a, Function<Integer, String> function) {
        return function.apply(a);
    }


    // 之前完成行為的做法. 提前把行為定義好,用的時候呼叫方法. 如:
    public  int method1(int a ){
        return a * 2 ;
    }

Function類中提供的預設方法的講解:

/**
     * Returns a composed function that first applies the {@code before}
     * function to its input, and then applies this function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     返回一個組合的函式。對應用完引數後的結果,再次執行apply
     *
     * @param <V> the type of input to the {@code before} function, and to the
     *           composed function  
     * @param before the function to apply before this function is applied
     * @return a composed function that first applies the {@code before}
     * function and then applies this function
     * @throws NullPointerException if before is null
     *
     * @see #andThen(Function)
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     *
     * @see #compose(Function)
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

compose : 組合function, 形成兩個function的串聯。 先執行引數

andThen :先應用當前的函式apply,然後再當做引數再次執行apply。 後執行引數。

identity:輸入什麼返回什麼。

BiFunction: 整合兩個函式的方法。
為什麼BiFunction不提供 compose ,只提供andThen呢?
因為如果提供compose方法的話,只能獲取一個引數的返回值。不合理。

  public static void main(String[] args) {
        FunctionTest2 functionTest2 = new FunctionTest2();

        // compose
//        System.out.println(functionTest2.compute(2,a -> a * 3,b -> b * b));
        // andThen
//        System.out.println(functionTest2.compute2(2,a -> a * 3,b -> b * b));

        //BiFunction
//        System.out.println(functionTest2.compute3(1,2, (a,b) -> a - b));
//        System.out.println(functionTest2.compute3(1,2, (a,b) -> a * b));
//        System.out.println(functionTest2.compute3(1,2, (a,b) -> a + b));
//        System.out.println(functionTest2.compute3(1,2, (a,b) -> a / b));

        //BiFunction  andThen
        System.out.println(functionTest2.compute4(2,3,(a,b) ->a + b , a -> a * a ));
    }

    //compose : 組合function, 形成兩個function的串聯。  先執行引數
    //andThen :先應用當前的函式apply,然後再當做引數再次執行apply。 後執行引數

    public int compute(int a, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
        return function1.compose(function2).apply(a);
    }

    public int compute2(int a, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
        return function1.andThen(function2).apply(a);
    }

    //BiFunction
    //求兩個引數的和
    //先定義一個抽象的行為.
    public int compute3(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
        return biFunction.apply(a, b);
    }

    //BiFunction  andThen 
    public int compute4(int a, int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
        return biFunction.andThen(function).apply(a, b);
    }

測試 函式式介面的例項:

public class PersonTest {
    public static void main(String[] args) {


        List<Person> personList = new ArrayList<>();

        personList.add(new Person("zhangsan", 20));
        personList.add(new Person("zhangsan", 28));
        personList.add(new Person("lisi", 30));
        personList.add(new Person("wangwu", 40));

        PersonTest test = new PersonTest();

        //測試 getPersonUsername
//        List<Person> personList1 = test.getPersonUsername("zhangsan", personList);
//        personList1.forEach(person -> System.out.println(person.getUsername()));


        //測試  getPersonByAge
        List<Person> personByAge = test.getPersonByAge(25, personList);
        personByAge.forEach(person -> System.out.println(person.getAge()));
        
        
        //測試第三種: 自定義輸入行為
        List<Person> list = test.getPersonByAge2(20,personList,(age,persons) ->{
            return persons.stream().filter(person -> person.getAge() > age).collect(Collectors.toList());
        });
        list.forEach(person -> System.out.println(person.getAge()));

    }


    public List<Person> getPersonUsername(String username, List<Person> personList) {
        return personList.stream().filter(person -> person.getUsername().equals(username)).collect(Collectors.toList());
    }

    public List<Person> getPersonByAge(int age, List<Person> personList) {
        //使用BiFunction的方式
//        BiFunction<Integer, List<Person>, List<Person>> biFunction = (ageOfPerson, list) -> {
//            return  list.stream().filter(person -> person.getAge() > ageOfPerson ).collect(Collectors.toList());
//        };

        //變換之後:
        BiFunction<Integer, List<Person>, List<Person>> biFunction = (ageOfPerson, list) ->
            list.stream().filter(person -> person.getAge() > ageOfPerson ).collect(Collectors.toList());

        return biFunction.apply(age, personList);
    }
    
     //第三種方式, 動作也讓使用者自己定義傳進來
    public List<Person> getPersonByAge2(int age ,List<Person> list,BiFunction<Integer,List<Person>,List<Person>> biFunction){
        return biFunction.apply(age, list);
    }
}

函式式介面的真諦: 傳遞的是行為,而不是資料

  public static void main(String[] args) {
        //給定一個輸入引數,判斷是否滿足條件,滿足的話返回true
        Predicate<String> predicate = p -> p.length() > 5;
        System.out.println(predicate.test("nnihaoda"));

    }

到現在為止,只是講解了Java.lang.function包下的幾個最重要的,經常使用的方法。


2020年01月01日19:03:33 新的一年開始,記錄一下每次學習的時間。

Predicate 謂語。 類中包含的方法:

  • boolean test(T t);
  • default Predicate<T> or(Predicate<? super T> other) 
  • default Predicate<T> negate()
  • default Predicate<T> and(Predicate<? super T> other)
  • static <T> Predicate<T> isEqual(Object targetRef)

函數語言程式設計,注重傳遞行為,而不是傳遞值。

public class PredicateTest2 {
    /**
     * 測試Predicate中的test方法
     */
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        PredicateTest2 predicateTest2 = new PredicateTest2();
        //獲取大於5的數字
        predicateTest2.getAllFunction(list,item -> item > 5);
        System.out.println("--------");
        //獲取所有的偶數
        predicateTest2.getAllFunction(list,item -> item % 2 ==0);
        System.out.println("--------");
        //獲取所有的數字
        predicateTest2.getAllFunction(list,item -> true);
        //獲取大於5並且是偶數的
        System.out.println("--------");
        predicateTest2.testAnd(list,item -> item > 5,item -> item % 2 == 0);
    }

    public void getAllFunction(List<Integer> list, Predicate<Integer> predicate){
        for (Integer integer : list) {
            if (predicate.test(integer)) {
                System.out.println(integer);
            }
        }
    }
  
     // test or  and
    public  void testAnd(List<Integer> list,Predicate<Integer>        integerPredicate,Predicate<Integer> integerPredicate1){
        for (Integer integer : list) {
            if (integerPredicate.and(integerPredicate1).test(integer)) {
                System.out.println(integer);
            }  
        }
    }
}

lambda表示式到底給我們帶來了什麼?原來通過面向物件的時候一個方法只能執行一種功能。現在傳遞的是行為,一個方法可以多次呼叫。

邏輯與或非三種的理解.


Supplier類 供應廠商;供應者 (不接收引數,返回結果)

用於什麼場合? 工廠


2020年1月3日08:06:28
BinaryOperator 介面

public class SinaryOpertorTest {

    public static void main(String[] args) {

        SinaryOpertorTest sinaryOpertorTest = new SinaryOpertorTest();

        System.out.println(sinaryOpertorTest.compute(1,2,(a,b) -> a+b));

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

        System.out.println(sinaryOpertorTest.getMax("hello123","world",(a,b) -> a.length() - b.length()));
    }

    private int compute(int a, int b, BinaryOperator<Integer> binaryOperator) {
        return binaryOperator.apply(a, b);
    }

    private String getMax(String a, String b, Comparator<String> comparator) {
        return BinaryOperator.maxBy(comparator).apply(a, b);
    }
}

Optional final :Optional 不要試圖用來當做引數, 一般只用來接收返回值,來規避值的空指標異常的問題。

  • empty()
  • of()
  • ofNullable()
  • isPresent()
  • get()
  • ...
public class OptionalTest {

    public static void main(String[] args) {
        Optional<String> optional = Optional.of("hello");

        //不確定是否為 空是 呼叫和這個方法
//        Optional<String> optional2 = Optional.ofNullable("hello");

//        Optional<String> optional1 = Optional.empty();


        //過時
//        if (optional.isPresent()) {
//            System.out.println(optional.get());
//        }

        optional.ifPresent(item -> System.out.println(item));
        System.out.println(optional.orElse("nihao"));
        System.out.println(optional.orElseGet(() -> "nihao"));

    }
public class OptionalTest2 {
    public static void main(String[] args) {
        Employee employee = new Employee();
        employee.setName("dawa");

        Employee employee1 = new Employee();
        employee1.setName("erwa");

        List<Employee> list = Arrays.asList(employee, employee1);
        Company company = new Company("gongsi", list);

        Optional<Company> optionalCompany = Optional.ofNullable(company);

        System.out.println(optionalCompany.map(company1 -> company1.getList()).orElse(Collections.emptyList()));
    }
}

Java8(2)方法引用詳解及Stream流介紹

2020你還不會Java8新特性?方法引用詳解及Stream 流介紹和操作方式詳解(三)

方法引用詳解

方法引用: method reference

方法引用實際上是Lambda表示式的一種語法糖

我們可以將方法引用看作是一個「函式指標」,function pointer

方法引用共分為4類:

  1. 類名::靜態方法名
  2. 引用名(物件名)::例項方法名
  3. 類名::例項方法名 (比較不好理解,個地方呼叫的方法只有一個引數,為什麼還能正常呼叫呢? 因為呼叫比較時,第一個物件來呼叫getStudentByScore1. 第二個物件來當做引數)
  4. 構造方法引用: 類名::new
public class StudentTest {

    public static void main(String[] args) {
        Student student = new Student("zhangsan",10);
        Student student1 = new Student("lisi",40);
        Student student2 = new Student("wangwu",30);
        Student student3 = new Student("zhaoliu",550);

        List<Student> list = Arrays.asList(student, student2, student3, student1);

//        list.forEach(item -> System.out.println(item.getName()));

        //1. 類名 :: 靜態方法
//        list.sort((studentpar1,studentpar2) -> Student.getStudentByScore(studentpar1,studentpar2));
        list.sort(Student::getStudentByScore);
        list.forEach(item -> System.out.println(item.getScore()));
        System.out.println(" - - - - - - - -- ");

        // 2. 引用名(物件名)::例項方法名
        StudentMethod studentMethod = new StudentMethod();
        list.sort(studentMethod::getStudentBySource);
        list.forEach(item -> System.out.println(item.getScore()));
        System.out.println(" - - - -- -- ");

        // 3. 類名:: 例項方法名
        // 這個地方呼叫的方法只有一個引數,為什麼還能正常呼叫呢? 因為呼叫比較時,第一個物件來呼叫getStudentByScore1. 第二個物件來當做引數
        list.sort(Student::getStudentByScore1);
        list.forEach(item -> System.out.println(item.getScore()));
        System.out.println("- - - - - - - -");

        // 原生的sort 來舉個例子
        List<String> list1 = Arrays.asList("da", "era", "a");
//        Collections.sort(list1,(city1,city2) -> city1.compareToIgnoreCase(city2));
        list1.sort(String::compareToIgnoreCase);
        list1.forEach(System.out::println);
        System.out.println("- - - - - - -- ");

        //4. 構造方法引用
        StudentTest studentTest = new StudentTest();
        System.out.println(studentTest.getString(String::new));
    }

    public String getString(Supplier<String> supplier) {
        return supplier.get()+"hello";
    }

}

預設方法

defaute method

預設方法是指實現此介面時,預設方法已經被預設實現。

引入預設方法最重要的作用就是Java要保證向後相容。

情景一: 一個類,實現了兩個介面。兩個介面中有一個相同名字的預設方法。此時會報錯,需要從寫這個重名的方法

情景二: 約定:實現類的優先順序比介面的優先順序要高。 一個類,實現一個介面,繼承一個實現類。介面和實現類中有一個同名的方法,此時,此類會使用實現類中的方法。


Stream 流介紹和操作方式詳解

Collection提供了新的stream()方法。

流不儲存值,通過管道的方式獲取值。

本質是函式式的,對流的操作會生成一個結果,不過並不會修改底層的資料來源,集合可以作為流的底層資料來源。

延遲查詢,很多流操作(過濾、對映、排序等)等可以延遲實現。

通過流的方式可以更好的操作集合。使用函數語言程式設計更為流程。與lambda表示式搭配使用。

流由3部分構成:

  1. 零個或多箇中間操作(操作的是誰?操作的是源)
  2. 終止操作(得到一個結果)

流操作的分類:

  1. 惰性求值(中間操作)
  2. 及早求值(種植操作)

使用鏈式的呼叫方式sunc as : stream.xxx().yyy().zzz().count(); 沒有count的時候前邊的三個方法不會被呼叫。後續會進行舉例。

掌握流常用的api,瞭解底層。

流支援並行化,可以多執行緒操作。迭代器不支援並行化。

流怎麼用?

流的建立方式

  1. 通過靜態方法 : Stream stream = Stream.of();
  2. 通過陣列:Arrays.stream();
  3. 通過集合建立物件:Stream stream = list.stream;

流的簡單應用

 public static void main(String[] args) {
        IntStream.of(1,2,4,5,6).forEach(System.out::println);
        IntStream.range(3, 8).forEach(System.out::println);
        IntStream.rangeClosed(3, 8).forEach(System.out::println);
    }

舉例:將一個數組中的數字都乘以二,然後求和。

 public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(list.stream().map(i -> i*2).reduce(0,Integer::sum));
    }

函數語言程式設計和傳統面向物件程式設計根本上有什麼不同?

傳統面向物件程式設計傳遞的是資料。函數語言程式設計通過方法傳遞的是一種行為,行為指導了函式的處理,根據行為對資料進行加工。

舉例:流轉換成list的練習

 public static void main(String[] args) {

        Stream<String> stream = Stream.of("hello", "world", "hello world");
//        String[] stringArray = stream.toArray(length -> new String[length]);
        //替換成方法引用的方式  --> 構造方法引用.
        String[] stringArray = stream.toArray(String[]::new);
        Arrays.asList(stringArray).forEach(System.out::println);
        System.out.println("- - - - - - - - - - -");

        //將流轉換成list,   有現成的封裝好的方法
        Stream<String> stream1 = Stream.of("hello", "world", "hello world");
        List<String> collect = stream1.collect(Collectors.toList());// 本身是一個終止操作
        collect.forEach(System.out::println);

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

        //使用原生的 collect 來將流轉成List
        Stream<String> stream2 = Stream.of("hello", "world", "hello world");
//        List<String> lis = stream2.collect(() -> new ArrayList(), (theList, item) -> theList.add(item),
//                (theList1, theList2) -> theList1.addAll(theList2));
        // 將上面的轉換成方法引用的方式  -- 這種方法不好理解.
        List<String> list = stream2.collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
        //這種方法,如果想要返回ArrayList也可以實現.
//        List<String> list1 = stream2.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        list.forEach(System.out::println);
    }

Collectors類中包含了流轉換的多個輔助類

舉例: 將流 轉成各種型別的資料。

public static void main(String[] args) {
        Stream<String> stream = Stream.of("hello", "world", "hello world");

        //將流轉換成List 另一種方法
//        List<String> list= stream.collect(Collectors.toCollection(ArrayList::new));
//        list.forEach(System.out::println);

        //將流轉成set
//        Set<String> set = stream.collect(Collectors.toSet());
        //轉成TreeSet
//        TreeSet<String> set = stream.collect(Collectors.toCollection(TreeSet::new));
//        set.forEach(System.out::println);

        //轉成字串
        String string = stream.collect(Collectors.joining());
        System.out.println(string);

        //Collectors 類中有多重輔助的方法.

    }

遇到問題的時候,先思考一下能否用方法引用的方式,使用流的方式來操作。因為用起來比較簡單。

舉例:將集合中的每一個元素 轉換成大寫的字母, 給輸出來。

public static void main(String[] args) {
        //將集合中的每一個元素 轉換成大寫的字母, 給輸出來
        List<String> list = Arrays.asList("hello","world","hello world");

        //轉成字串,然後轉成大寫.
//        System.out.println(list.stream().collect(Collectors.joining()).toUpperCase());
        //上面的程式碼 可以轉換成下邊的程式碼.
//        System.out.println(String.join("", list).toUpperCase());

        //視訊上給出的   還是List的大寫
        list.stream().map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
  
  //將集合 的資料給平方一下輸出.
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
        list1.stream().map(item -> item * item).collect(Collectors.toList()).forEach(System.out::println);

}

流中的 .map () 方法,是對集合中的每一個數據進行一下操作。

stream 的 flat操作。 打平操作。

public static void main(String[] args) {
// 舉例:   flag 的操作, 打平. 一個集合中有三個陣列, 打平之後,三個陣列的元素依次排列.
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5));
//將裡邊每一個ArrayList的資料 做一個平方.  然後打平. 輸出一個list
stream.flatMap(theList -> theList.stream()).map(item -> item * item).forEach(System.out::println);
}

Stream 其他方法介紹:

public static void main(String[] args) {
//        stream 其他方法介紹.

        //  generate(). 生成stream物件
        Stream<String> stream = Stream.generate(UUID.randomUUID()::toString);
//        System.out.println(stream.findFirst().get());
// findFirst,找到第一個物件.然後就短路了,會返回一個Optional物件(為了避免NPE),不符合函數語言程式設計
//        stream.findFirst().isPresent(System.out::print);

        // iterate()     會生成 一個 無限的序列流.
        // 一般不會單獨使用. 會使用limit  來限制一下總長度.
        Stream.iterate(1, item -> item + 2).limit(6).forEach(System.out::println);
    }

Stream 運算練習:(Stream提供了各種操作符)

舉例:找出該流中大於2的元素,然後每個元素*2 ,然後忽略掉流中的前兩個元素,然後再取流中的前兩個元素,最後求出流元素中的總和.

 Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(6);
        //找出該流中大於2的元素,先使用filter()過濾.
        //每個元素*2 使用mapToInt 避免重複拆箱.
        //忽略掉流中的前兩個元素; 使用 skip(2)
        //再取流中的前兩個元素;  使用limit(2)
        //求出流元素中的總和.  使用sum()
System.out.println(stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).sum());

舉例:找出該流中大於2的元素,然後每個元素*2 ,然後忽略掉流中的前兩個元素,然後再取流中的前兩個元素,最後找到最小的元素.

    // .min() 返回的是IntOptional.
//        System.out.println(stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).min());
        //應該這樣呼叫. 上邊的可能會出NPE異常
        stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).min().ifPresent(System.out::println);

舉例:獲取最大值,最小值,求和等各種操作。 .summaryStatistics();

在練習的過程中發現了一個問題。如果是這樣連續列印兩條對流操作之後的結果。會報流未關閉的異常。

注意事項:流被重複使用了,或者流被關閉了,就會出異常。

如何避免:使用方法鏈的方式來處理流。 具體出現的原因,後續進行詳細的原始碼講解。


舉例 :中間操作(惰性求值) 和中止操作(及早求值)本質的區別

public static void main(String[] args) {
    List<String> list = Arrays.asList("hello", "world", "hello world");

    //首字母轉大寫
    list.stream().map(item ->{
        String s = item.substring(0, 1).toUpperCase() + item.substring(1);
        System.out.println("test");
        return s;
    }).forEach(System.out::println);
    //沒有遇到中止操作時,是不會執行中間操作的.是延遲的
    // 遇到.forEach() 中止操作時,才會執行中間操作的程式碼
}

舉例:流使用順序不同的區別

//程式不會停止
IntStream.iterate(0,i->(i+1)%2).distinct().limit(6).forEach(System.out::println);
//程式會停止
IntStream.iterate(0,i->(i+1)%2).limit(6).distinct().forEach(System.out::println);

Stream底層深入

  • 和迭代器不同的是,Stream可以並行化操作,迭代器只能命令式地、序列化操作

  • 當使用穿行方式去遍歷時,每個item讀完後再讀下一個item
  • 使用並行去遍歷時,資料會被分成多個段,其中每一個都在不同的執行緒中處理,然後將結果一起輸出。
  • Stream的並行操作依賴於Java7中引入的Fork/Join框架。

流(Stream)由3部分構成:

  1. 源(Source)
  2. 零個或多箇中間操作(Transforming values)(操作的是誰?操作的是源)
  3. 終止操作(Operations)(得到一個結果)

內部迭代和外部迭代

描述性的語言:sql和Stream的對比

select name from student where age > 20 and address = 'beijing' order by desc;

===================================================================================

Student.stream().filter(student -> student.getAge >20 ).filter(student -> student.getAddress().equals("beijing")).sorted(..).forEach(student -> System.out.println(student.getName));

上述的描述,並沒有明確的告訴底層具體要怎麼做,只是發出了描述性的資訊。這種流的方式就叫做內部迭代。針對於效能來說,流的操作肯定不會降低效能。

外邊迭代舉例: jdk8以前的用的方式。

List list = new ArrayList<>();

for(int i = 0 ;i <= students.size();i++){

​ Student student = students.get(i);

If(student.getAge() > 20 )

​ list.add(student);

}

Collections.sort(list.....)

list.forEach().....

Stream的出現和集合是密不可分的。

集合關注的是資料與資料儲存本身,流關注的則是對資料的計算。

流與迭代器類似的一點是:流是無法重複使用或消費的。

如何區分中間操作和中止操作:

中間操作都會返回一個Stream物件,比如說返回Stream,Stream,Stream;

中止操作則不會返回Stream型別,可能不返回值,也可能返回其他型別的單個值。


並行流的基本使用

舉例: 序列流和並行流的簡單舉例比較

public static void main(String[] args) {
    // 序列流和並行流的比較
    List<String> list = new ArrayList<>(5000000);

    for (int i = 0; i < 5000000; i++) {
        list.add(UUID.randomUUID().toString());
    }

    System.out.println("開始排序");
    long startTime = System.nanoTime();
    //   list.parallelStream().sorted().count(); //序列流
    list.parallelStream().sorted().count(); //並行流
    long endTime = System.nanoTime();
    long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
    System.out.println("排序時間為: "+ millis);
}

結果如圖,並行流和序列流時間上錯了4倍。

舉例: 打印出列表中出來第一個長度為5的單詞.. 同時將長度5打印出來.

 public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world", "hello world");

//        list.stream().mapToInt(item -> item.length()).filter(length -> length ==5)
//                      .findFirst().ifPresent(System.out::println);

        list.stream().mapToInt(item -> {
            int length = item.length();
            System.out.println(item);
            return length;
        }).filter(length -> length == 5).findFirst().ifPresent(System.out::println);
    //返回的是hello  , 不包含 world.  
    }

返回的是hello , 不包含 world.

流的操作原理: 把流想成一個容器,裡邊儲存的是對每一個元素的操作。操作時,把操作序列化。對同一個元素進行序列的操作。操作中還包含著短路操作。

舉例: 找出 這個集合中所有的單詞,而且要去重. flatMap()的使用。

public static void main(String[] args) {
        //舉例; 找出 這個集合中所有的單詞,而且要去重.
        List<String> list = Arrays.asList("hello welcome", "world hello", "hello world", "hello hello world");

//        list.stream().map(item -> item.split(" ")).distinct()
//                .collect(Collectors.toList()).forEach(System.out::println);

        //使用map不能滿足需求, 使用flatMap
        list.stream().map(item -> item.split(" ")).flatMap(Arrays::stream)
                .distinct().collect(Collectors.toList()).forEach(System.out::println);

        //結果為  hello  welcome world 
    }

舉例:組合起來. 打印出 hi zhangsan , hi lisi , hi wangwu , hello zhangsan , hello lisi .... flatMap()的使用。

public static void main(String[] args) {

    //組合起來. 打印出  hi zhangsan , hi lisi , hi wangwu , hello zhangsan , hello lisi ....
    List<String> list = Arrays.asList("Hi", "Hello", "你好");
    List<String> list1 = Arrays.asList("zhangsan", "lisi", "wangwu");

    List<String> collect = list.stream().flatMap(item -> list1.stream().map(item2 -> item + " " +
            item2)).collect(Collectors.toList());

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

}

舉例: 流對分組/分割槽操作的支援. group by / protition by

 public static void main(String[] args) {
        //資料準備.
        Student student1 = new Student("zhangsan", 100, 20);
        Student student2 = new Student("lisi", 90, 20);
        Student student3 = new Student("wangwu", 90, 30);
        Student student4 = new Student("zhangsan", 80, 40);
        List<Student> students = Arrays.asList(student1, student2, student3, student4);

        //對學生按照姓名分組.
        Map<String, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getName));
        System.out.println(listMap);

        //對學生按照分數分組.
        Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getScore));
        System.out.println(collect);

        //按照年齡分組.
        Map<Integer, List<Student>> ageMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
        System.out.println(ageMap);

        //按照名字分組後,獲取到每個分組的元素的個數.
        Map<String, Long> nameCount = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
        System.out.println(nameCount);

        //按照名字分組,求得每個組的平均值.
        Map<String, Double> doubleMap = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
        System.out.println(doubleMap);
   
            //分割槽,  分組的一種特例. 只能分兩個組 true or flase .   partitioning  By
        Map<Boolean, List<Student>> collect1 = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() >= 90));
        System.out.println(collect1);

    }

Java8(3)Collector類原始碼分析

繼續學習Java8 新特性。

Collector類原始碼分析2020了你還不會Java8新特性?

jdk8是怎麼對底層完成支援的。不瞭解底層,平時用還可以,但是遇到問題的時候就會卡在那裡。遲遲滅有解決方案。在學習一門新技術時,先學習怎麼去用,不要執著於原始碼。但是隨著用的越來越多,你去了解底層是比較好的一種學習方法。

有多種方法可以實現同一個功能.什麼方式更好呢? 越具體的方法越好.   減少自動裝箱拆箱操作
  1. collect : 收集器
  2. Collector作為collect方法的引數。
  3. Collector作為一個介面。它是一個可變的匯聚操作,將輸入元素累計到一個可變的結果容器中;它會在所有元素都處理完畢後將累計的結果作為一個最終的表示(這是一個可選操作);它支援序列與並行兩種方式執行。(並不是說並行一定比序列快。)
  4. Collects本身提供了關於Collectoe的常見匯聚實現,Collectors本身實際上是一個工廠。
  5. 為了確保序列和並行的結果一致,需要進行額外的處理。必須要滿足兩個約束。
    identity 同一性
    associativity 結合性
  6. 同一性:對於任何一條並行線路來說 ,需要滿足a == combiner.apply(a, supplier.get())。舉例來說:
    (List list1,List list2 -> {list1.addAll(list2);return list1})
    結合性: 下方有舉例。

Collector收集器的實現原始碼詳解

/**
 * A <a href="package-summary.html#Reduction">mutable reduction operation</a> that
 * accumulates input elements into a mutable result container, optionally transforming
 * the accumulated result into a final representation after all input elements
 * have been processed.  Reduction operations can be performed either sequentially
 * or in parallel.
 
    Collector作為一個介面。它是一個可變的匯聚操作,將輸入元素累計到一個可變的結果容器中;它會在所有元素都處理   完畢後將累計的結果作為一個最終的表示(這是一個可選操作);它支援序列與並行兩種方式執行。(並不是說並行一定比序列快。)
  
 * <p>Examples of mutable reduction operations include:
 * accumulating elements into a {@code Collection}; concatenating
 * strings using a {@code StringBuilder}; computing summary information about
 * elements such as sum, min, max, or average; computing "pivot table" summaries
 * such as "maximum valued transaction by seller", etc.  The class {@link Collectors}
 * provides implementations of many common mutable reductions.
 
 Collects本身提供了關於Collectoe的常見匯聚實現,Collectors本身實際上是一個工廠。
 
 * <p>A {@code Collector} is specified by four functions that work together to
 * accumulate entries into a mutable result container, and optionally perform
 * a final transform on the result.  They are: <ul>
 *     <li>creation of a new result container ({@link #supplier()})</li>
 *     <li>incorporating a new data element into a result container ({@link #accumulator()})</li>
 *     <li>combining two result containers into one ({@link #combiner()})</li>
 *     <li>performing an optional final transform on the container ({@link #finisher()})</li>
 * </ul>
 
    Collector 包含了4個引數
 
 * <p>Collectors also have a set of characteristics, such as
 * {@link Characteristics#CONCURRENT}, that provide hints that can be used by a
 * reduction implementation to provide better performance.
 *
 * <p>A sequential implementation of a reduction using a collector would
 * create a single result container using the supplier function, and invoke the
 * accumulator function once for each input element.  A parallel implementation
 * would partition the input, create a result container for each partition,
 * accumulate the contents of each partition into a subresult for that partition,
 * and then use the combiner function to merge the subresults into a combined
 * result.
 
   舉例說明: 
  1,2, 3, 4     四個部分結果。
  1,2 -》 5  
  5,3 -》 6
  6,4 -》 6  
 
 
 ### 同一性和結合性的解析: 
 
 * <p>To ensure that sequential and parallel executions produce equivalent
 * results, the collector functions must satisfy an <em>identity</em> and an
 * <a href="package-summary.html#Associativity">associativity</a> constraints.
 
 為了確保序列和並行的結果一致,需要進行額外的處理。必須要滿足兩個約束。
             identity 同一性
                associativity  結合性
 
 * <p>The identity constraint says that for any partially accumulated result,
 * combining it with an empty result container must produce an equivalent
 * result.  That is, for a partially accumulated result {@code a} that is the
 * result of any series of accumulator and combiner invocations, {@code a} must
 * be equivalent to {@code combiner.apply(a, supplier.get())}.
  
    同一性: 對於任何一條並行線路來說,需要滿足a == combiner.apply(a, supplier.get())
 
 * <p>The associativity constraint says that splitting the computation must
 * produce an equivalent result.  That is, for any input elements {@code t1}
 * and {@code t2}, the results {@code r1} and {@code r2} in the computation
 * below must be equivalent:
 * <pre>{@code
 *     A a1 = supplier.get();               序列: 
 *     accumulator.accept(a1, t1);  第一個引數,每次累加的中間結果。 第二個引數,下一個要處理的引數
 *     accumulator.accept(a1, t2);
 *     R r1 = finisher.apply(a1);  // result without splitting
 *      
 *     A a2 = supplier.get();               並行: 
 *     accumulator.accept(a2, t1);  第一個引數,每次累加的中間結果。 第二個引數,下一個要處理的引數
 *     A a3 = supplier.get();
 *     accumulator.accept(a3, t2);
 *     R r2 = finisher.apply(combiner.apply(a2, a3));  // result with splitting
 * } </pre>
 
 結合性: 如上例。  最終要求 r1 == r2 
 
 * <p>For collectors that do not have the {@code UNORDERED} characteristic,
 * two accumulated results {@code a1} and {@code a2} are equivalent if
 * {@code finisher.apply(a1).equals(finisher.apply(a2))}.  For unordered
 * collectors, equivalence is relaxed to allow for non-equality related to
 * differences in order.  (For example, an unordered collector that accumulated
 * elements to a {@code List} would consider two lists equivalent if they
 * contained the same elements, ignoring order.)

 對於無序的收集器來說,等價性就被放鬆了,會考慮到順序上的區別對應的不相等性。
 兩個集合中包含了相同的元素,但是忽略了順序。這種情況下兩個的集合也是等價的。
 
 
 ### collector複合與注意事項:
 
 * <p>Libraries that implement reduction (匯聚) based on {@code Collector}, such as
 * {@link Stream#collect(Collector)}, must adhere to the following constraints:
 * <ul>  
 *     <li>The first argument passed to the accumulator function, both
 *     arguments passed to the combiner function, and the argument passed to the
 *     finisher function must be the result of a previous invocation of the
 *     result supplier, accumulator, or combiner functions.</li>
                
 *     <li>The implementation should not do anything with the result of any of
 *     the result supplier, accumulator, or combiner functions other than to
 *     pass them again to the accumulator, combiner, or finisher functions,
 *     or return them to the caller of the reduction operation.</li>
 
             具體的實現來說,不應該對中間返回的結果進行額外的操作。除了最終的返回的結果。
        
 *     <li>If a result is passed to the combiner or finisher
 *     function, and the same object is not returned from that function, it is
 *     never used again.</li>
 
            如果一個結果被傳遞給combiner or finisher,但是並沒有返回一個你傳遞的物件,說明你生成了一個新的結果或者建立了新的物件。這個結果就不會再被使用了。
            
 *     <li>Once a result is passed to the combiner or finisher function, it
 *     is never passed to the accumulator function again.</li>
 
                一旦一個結果被傳遞給了 combiner or finisher 函式,他就不會再被傳遞給了accumulator函數了。
 
 *     <li>For non-concurrent collectors, any result returned from the result
 *     supplier, accumulator, or combiner functions must be serially
 *     thread-confined.  This enables collection to occur in parallel without
 *     the {@code Collector} needing to implement any additional synchronization.
 *     The reduction implementation must manage that the input is properly
 *     partitioned, that partitions are processed in isolation, and combining
 *     happens only after accumulation is complete.</li>
 
            執行緒和執行緒之間的處理都是獨立的,最終結束時再進行合併。
            
 *     <li>For concurrent collectors, an implementation is free to (but not
 *     required to) implement reduction concurrently.  A concurrent reduction
 *     is one where the accumulator function is called concurrently from
 *     multiple threads, using the same concurrently-modifiable result container,
 *     rather than keeping the result isolated during accumulation.
 *     A concurrent reduction should only be applied if the collector has the
 *     {@link Characteristics#UNORDERED} characteristics or if the
 *     originating data is unordered.</li>
    
            如果不是併發收集器,4個執行緒會生成4箇中間結果。
            是併發收集器的話,4個執行緒會同時呼叫一個結果容器。
 
 * </ul>
 *
 * <p>In addition to the predefined implementations in {@link Collectors}, the
 * static factory methods {@link #of(Supplier, BiConsumer, BinaryOperator, Characteristics...)}
 * can be used to construct collectors.  For example, you could create a collector
 * that accumulates widgets into a {@code TreeSet} with:
 *
 * <pre>{@code
 *     Collector<Widget, ?, TreeSet<Widget>> intoSet =
 *         Collector.of(TreeSet::new, TreeSet::add,
 *                      (left, right) -> { left.addAll(right); return left; });
 * }</pre>
 
 通過Collector.of(傳進一個新的要操作的元素,結果容器處理的步驟,多執行緒處理的操作)
 將流中的每個Widget 新增到TreeSet中
 
 
 * (This behavior is also implemented by the predefined collector
 * {@link Collectors#toCollection(Supplier)}).
 *
 * @apiNote  
 * Performing a reduction operation with a {@code Collector} should produce a
 * result equivalent to:
 * <pre>{@code
 *     R container = collector.supplier().get();
 *     for (T t : data)
 *         collector.accumulator().accept(container, t);
 *     return collector.finisher().apply(container);
 * }</pre>
 
    api的說明:  collector的finisher匯聚的實現過程。
 
 * <p>However, the library is free to partition the input, perform the reduction
 * on the partitions, and then use the combiner function to combine the partial
 * results to achieve a parallel reduction.  (Depending on the specific reduction
 * operation, this may perform better or worse, depending on the relative cost
 * of the accumulator and combiner functions.)
 
 效能取決於accumulator and combiner的代價。  也就是說 並行流 並不一定比序列流效率高。
 
 * <p>Collectors are designed to be <em>composed</em>; many of the methods
 * in {@link Collectors} are functions that take a collector and produce
 * a new collector.  For example, given the following collector that computes
 * the sum of the salaries of a stream of employees:
 * <pre>{@code
 *     Collector<Employee, ?, Integer> summingSalaries
 *         = Collectors.summingInt(Employee::getSalary))
 * }</pre>
 
  蒐集器是可以組合的:  take a collector and produce a new collector.  
  蒐集器的實現過程。  如  員工的工資的求和。
 
 * If we wanted to create a collector to tabulate the sum of salaries by
 * department, we could reuse the "sum of salaries" logic using
 * {@link Collectors#groupingBy(Function, Collector)}:
 * <pre>{@code
 *     Collector<Employee, ?, Map<Department, Integer>> summingSalariesByDept
 *         = Collectors.groupingBy(Employee::getDepartment, summingSalaries);
 * }</pre>
 
 如果我們想要新建一個蒐集器,我們可以複用之前的蒐集器。
 實現過程。
 
 * @see Stream#collect(Collector)
 * @see Collectors
 *
 * @param <T> the type of input elements to the reduction operation
            <T>  代表 流中的每一個元素的型別。
 * @param <A> the mutable accumulation type of the reduction operation (often
 *            hidden as an implementation detail)
            <A>  代表 reduction操作的可變容器的型別。表示中間操作生成的結果的型別(如ArrayList)。
 * @param <R> the result type of the reduction operation
            <R>  代表 結果型別
 * @since 1.8
 */
public interface Collector<T, A, R>{
   /**
     * A function that creates and returns a new mutable result container.
     *  A就代表每一次返回結果的型別
     * @return a function which returns a new, mutable result container
     */
    Supplier<A> supplier();   // 提供一個結果容器
    
    /**
     * A function that folds a value into a mutable result container.
     *  A代表中間操作返回結果的型別。 T是下一個代操作的元素的型別。
     * @return a function which folds a value into a mutable result container
     */
    BiConsumer<A, T> accumulator();    //不斷的向結果容器中新增元素。

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     *  A 中間操作返回結果的型別。
     * @return a function which combines two partial results into a combined
     * result
     */
    BinaryOperator<A> combiner();   //在多執行緒中  合併 部分結果。 
  /**
  和並行流緊密相關的
    接收兩個結果,將兩個部分結果合併到一起。
  combiner函式,有4個執行緒同時去執行,那麼就會有生成4個部分結果。
  
  舉例說明: 
  1,2, 3, 4     四個部分結果。
  1,2 -》 5  
  5,3 -》 6
  6,4 -》 6  
   1,2合併返回5  屬於return a new result container. 
   6,4合併返回6,屬於The combiner function may fold state from one argument into the other and  return that。
   */

    /**
     * Perform the final transformation from the intermediate accumulation type
     * {@code A} to the final result type {@code R}.
     *R 是最終返回結果的型別。
     * <p>If the characteristic {@code IDENTITY_TRANSFORM} is
     * set, this function may be presumed to be an identity transform with an
     * unchecked cast from {@code A} to {@code R}.
     * 
     * @return a function which transforms the intermediate result to the final
     * result
     */
    Function<A, R> finisher();  //  合併中間的值,給出返回值。
  
    /**
     * Returns a {@code Set} of {@code Collector.Characteristics} indicating
     * the characteristics of this Collector.  This set should be immutable.
     *
     * @return an immutable set of collector characteristics
     */
    Set<Characteristics> characteristics();   //特徵的集合

    /**
     * Returns a new {@code Collector} described by the given {@code supplier},
     * {@code accumulator}, and {@code combiner} functions.  The resulting
     * {@code Collector} has the {@code Collector.Characteristics.IDENTITY_FINISH}
     * characteristic.
     *
     * @param supplier The supplier function for the new collector
     * @param accumulator The accumulator function for the new collector
     * @param combiner The combiner function for the new collector
     * @param characteristics The collector characteristics for the new
     *                        collector
     * @param <T> The type of input elements for the new collector
     * @param <R> The type of intermediate accumulation result, and final result,
     *           for the new collector
     * @throws NullPointerException if any argument is null
     * @return the new {@code Collector}