1. 程式人生 > 實用技巧 >【Java】Java8新特性

【Java】Java8新特性

Java 8新特性彙總

Java 8的改進

速度更快

程式碼更少(增加了新的語法:Lambda表示式)

引入強大的Stream APl

便於並行

最大化減少空指標異常:Optional

Nashorn引擎,允許在JVM上執行JS應用

並行流就是把一個內容分成多個數據塊,並用不同的執行緒分別處理每個資料塊的流。相比較序列的流,並行的流可以很大程度上提高程式的執行效率。

Java 8中將並行進行了優化,我們可以很容易的對資料進行並行操作。Stream API可以宣告性地通過 parallel()與 sequential()在並行流與順序流之間進行切換

一、Lambda表示式

1. Lamdba表示式概述

Lambda是一個匿名函式,可以把Lambda表示式理解為是一段可以傳遞的程式碼(將程式碼像資料一樣進行傳遞)。使用它可以寫出更簡潔、更靈活的程式碼。作為一種更緊湊的程式碼風格,使Java的語言表達能力得到了提升。

2. 使用Lambda表示式前後對比

示例一:呼叫Runable介面

@Test
public void test1(){
    //未使用Lambda表示式的寫法
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("hello Lambda!");
        }
    };
    r1.run();

    System.out.println("========================");
    //Lamdba表示式寫法
    Runnable r2 = () -> System.out.println("hi Lambda!");
    r2.run();
}

示例二:使用Comparator介面

@Test
public void test2(){
    //未使用Lambda表示式的寫法
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1,o2);
        }
    };

    int compare1 = com1.compare(12, 32);
    System.out.println(compare1);//-1
    System.out.println("===================");

    //Lambda表示式的寫法
    Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2);

    int compare2 = com2.compare(54, 21);
    System.out.println(compare2);//1
    System.out.println("===================");

    //方法引用
    Comparator<Integer> cpm3 = Integer::compareTo;
    int compare3 = cpm3.compare(12, 12);
    System.out.println(compare3);//0
}

3. 怎樣使用Lambda表示式

3.1 Lamdba表示式基本語法

1.舉例: (o1,o2) -> Integer.compare(o1,o2);

2.格式:
-> :lambda操作符 或 箭頭操作符
-> 左邊:lambda形參列表 (其實就是介面中的抽象方法的形參列表)
-> 右邊:lambda體 (其實就是重寫的抽象方法的方法體)

3.2Lamdba表示式使用(包含六種情況)

3.2.1 語法格式一:無參,有返回值

Runnable r1 = () -> {System.out.println(“hello Lamdba!”)}

3.2.2語法格式二:Lamdba需要一個引數,但沒有返回值

Consumer<String> con = (String str) -> {System.out.println(str)}

3.2.3 語法格式三:資料型別可省略,因為可由編譯器推斷得出,稱為型別推斷

Consumer<String> con = (str) -> {System.out.println(str)}

3.2.4 語法格式四:Lamdba若只需要一個引數時,小括號可以省略

Consumer<String> con = str -> {System.out.println(str)}

3.2.5 語法格式五:Lamdba需要兩個以上的引數,多條執行語句,並且可以有返回值

Comparator<Integer>com = (o1,o1) -> {
    Syste.out.println("Lamdba表示式使用");
    return Integer.compare(o1,o2);
}

3.2.6 語法格式六:當Lamdba體只有一條語句時,return和大括號若有,都可以省略

Comparator<Integer>com = (o1,o1) -> Integer.compare(o1,o2);

程式碼示例:

public class LamdbaTest2 {
    //語法格式一:無參,無返回值
    @Test
    public void test1() {
        //未使用Lambda表示式
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Lamdba");
            }
        };
        r1.run();
        System.out.println("====================");
        //使用Lambda表示式
        Runnable r2 = () -> {
            System.out.println("Hi Lamdba");
        };
        r2.run();
    }

    //語法格式二:Lambda 需要一個引數,但是沒有返回值。
    @Test
    public void test2() {
        //未使用Lambda表示式
        Consumer<String> con = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con.accept("你好啊Lambda!");
        System.out.println("====================");
        //使用Lambda表示式
        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("我是Lambda");

    }

    //語法格式三:資料型別可以省略,因為可由編譯器推斷得出,稱為“型別推斷”
    @Test
    public void test3() {
        //未使用Lambda表示式
        Consumer<String> con = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con.accept("你好啊Lambda!");
        System.out.println("====================");
        //使用Lambda表示式
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("我是Lambda");
    }

    @Test
    public void test(){
        ArrayList<String> list = new ArrayList<>();//型別推斷,用左邊推斷右邊
        int[] arr = {1,2,3,4};//型別推斷,用左邊推斷右邊
    }

    //語法格式四:Lambda 若只需要一個引數時,引數的小括號可以省略
    @Test
    public void test4() {
        //未使用Lambda表示式
        Consumer<String> con = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con.accept("你好啊Lambda!");
        System.out.println("====================");
        //使用Lambda表示式
        Consumer<String> con1 = s -> {
            System.out.println(s);
        };
        con1.accept("我是Lambda");
    }

    //語法格式五:Lambda 需要兩個或以上的引數,多條執行語句,並且可以有返回值
    @Test
    public void test5() {
        //未使用Lambda表示式
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println(o1);
                System.out.println(o2);
                return Integer.compare(o1, o2);
            }
        };
        System.out.println(com1.compare(23, 45));
        System.out.println("====================");
        //使用Lambda表示式
        Comparator<Integer> com2 = (o1, o2) -> {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
        System.out.println(com2.compare(23, 12));
    }

    //語法格式六:當 Lambda 體只有一條語句時,return 與大括號若有,都可以省略
    @Test
    public void test6() {
        //未使用Lambda表示式
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
        System.out.println(com1.compare(23, 45));
        System.out.println("====================");
        //使用Lambda表示式
        Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);

        System.out.println(com2.compare(23, 12));
    }
    @Test
    public void test7(){
        //未使用Lambda表示式
        Consumer<String> con1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con1.accept("hi!");
        System.out.println("====================");
        //使用Lambda表示式
        Consumer<String> con2 = s -> System.out.println(s);
        con2.accept("hello");
    }

}

3.3 Lambda表示式使用總結

-> 左邊:lambda形參列表的引數型別可以省略(型別推斷);如果lambda形參列表只有一個引數,其一對()也可以省略
-> 右邊:lambda體應該使用一對{}包裹;如果lambda體只有一條執行語句(可能是return語句),省略這一對{}和return關鍵字

4. Lamdba表示式總結

Lambda表示式的本質:作為函式式介面的例項
如果一個介面中,只聲明瞭一個抽象方法,則此介面就稱為函式式介面。我們可以在一個介面上使用 @FunctionalInterface 註解,這樣做可以檢查它是否是一個函式式介面。
因此以前用匿名實現類表示的現在都可以用Lambda表示式來寫。

二、函式式介面

1. 函式式介面概述

只包含一個抽象方法的介面,稱為函式式介面。

可以通過Lambda表示式來建立該介面的物件。(若Lambda表示式丟擲一個受檢異常(即:非執行時異常),那麼該異常需要在目標介面的抽象方法上進行宣告)。

可以在一個介面上使用@FunctionalInterface註解,這樣做可以檢查它是否是一個函式式介面。同時 javadoc也會包含一條宣告,說明這個介面是一個函式式介面。

Lambda表示式的本質:作為函式式介面的例項

在 java.util.function 包下定義了Java 8的豐富的函式式介面

自定義函式式介面

@FunctionalInterface
public interface MyInterface {
    void method1();
}

3. Java內建函式式介面

3.1四大核心函式式介面

應用舉例

public class LambdaTest3 {
    //    消費型介面 Consumer<T>     void accept(T t)
    @Test
    public void test1() {
        //未使用Lambda表示式
        Learn("java", new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println("學習什麼? " + s);
            }
        });
        System.out.println("====================");
        //使用Lambda表達
        Learn("html", s -> System.out.println("學習什麼? " + s));

    }

    private void Learn(String s, Consumer<String> stringConsumer) {
        stringConsumer.accept(s);
    }

    //    供給型介面 Supplier<T>     T get()
    @Test
    public void test2() {
        //未使用Lambdabiaodas
        Supplier<String> sp = new Supplier<String>() {
            @Override
            public String get() {
                return new String("我能提供東西");
            }
        };
        System.out.println(sp.get());
        System.out.println("====================");
        //使用Lambda表達
        Supplier<String> sp1 = () -> new String("我能通過lambda提供東西");
        System.out.println(sp1.get());
    }

    //函式型介面 Function<T,R>   R apply(T t)
    @Test
    public void test3() {
        //使用Lambda表示式
        Employee employee = new Employee(1001, "Tom", 45, 10000);

        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));

    }

    //斷定型介面 Predicate<T>    boolean test(T t)
    @Test
    public void test4() {
        //使用匿名內部類
        Function<Double, Long> func = new Function<Double, Long>() {
            @Override
            public Long apply(Double aDouble) {
                return Math.round(aDouble);
            }
        };
        System.out.println(func.apply(10.5));
        System.out.println("====================");

        //使用Lambda表示式
        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));

    }
}

3.2 其他函式式介面

4. 使用總結

4.1 何時使用lambda表示式?

當需要對一個函式式介面例項化的時候,可以使用lambda表示式。

4.2 何時使用給定的函式式介面?

如果我們開發中需要定義一個函式式介面,首先看看在已有的jdk提供的函式式介面是否提供了能滿足需求的函式式介面。如果有,則直接呼叫即可,不需要自己再自定義了。

三、方法的引用

1. 方法引用概述

方法引用可以看做是Lambda表示式深層次的表達。換句話說,方法引用就是Lambda表示式,也就是函式式介面的一個例項,通過方法的名字來指向一個方法。

2. 使用情景

當要傳遞給Lambda體的操作,已經實現的方法了,可以使用方法引用!

3. 使用格式

類(或物件) :: 方法名

4. 使用情況

情況1 物件 :: 非靜態方法

情況2 類 :: 靜態方法

情況3 類 :: 非靜態方法

5. 使用要求

要求介面中的抽象方法的形參列表和返回值型別與方法引用的方法的形參列表和返回值型別相同!(針對於情況1和情況2)
當函式式介面方法的第一個引數是需要引用方法的呼叫者,並且第二個引數是需要引用方法的引數(或無引數)時:ClassName::methodName(針對於情況3)

6. 使用建議

如果給函式式介面提供例項,恰好滿足方法引用的使用情境,就可以考慮使用方法引用給函式式介面提供例項。如果不熟悉方法引用,那麼還可以使用lambda表示式。

7. 使用舉例

public class MethodRefTest {

    // 情況一:物件 :: 例項方法
    //Consumer中的void accept(T t)
    //PrintStream中的void println(T t)
    @Test
    public void test1() {
        //使用Lambda表達
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("中國");
        System.out.println("====================");

        //使用方法引用
        PrintStream ps = System.out;
        Consumer con2 = ps::println;
        con2.accept("China");

    }

    //Supplier中的T get()
    //Employee中的String getName()
    @Test
    public void test2() {
        //使用Lambda表達
        Employee emp = new Employee(1001, "Bruce", 34, 600);
        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());
        System.out.println("====================");

        //使用方法引用
        Supplier sup2 = emp::getName;
        System.out.println(sup2.get());


    }

    // 情況二:類 :: 靜態方法
    //Comparator中的int compare(T t1,T t2)
    //Integer中的int compare(T t1,T t2)
    @Test
    public void test3() {
        //使用Lambda表達
        Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(com1.compare(32, 45));
        System.out.println("====================");

        //使用方法引用
        Comparator<Integer> com2 = Integer::compareTo;
        System.out.println(com2.compare(43, 34));
    }

    //Function中的R apply(T t)
    //Math中的Long round(Double d)
    @Test
    public void test4() {
        //使用匿名內部類
        Function<Double, Long> func = new Function<Double, Long>() {
            @Override
            public Long apply(Double aDouble) {
                return Math.round(aDouble);
            }
        };
        System.out.println(func.apply(10.5));
        System.out.println("====================");

        //使用Lambda表示式
        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)
    @Test
    public void test5() {
        //使用Lambda表示式
        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abd", "aba"));
        System.out.println("====================");

        //使用方法引用
        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("abd", "abc"));
    }

    //BiPredicate中的boolean test(T t1, T t2);
    //String中的boolean t1.equals(t2)
    @Test
    public void test6() {
        //使用Lambda表示式
        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();
    @Test
    public void test7() {
        //使用Lambda表示式
        Employee employee = new Employee(1001, "Tom", 45, 10000);

        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.使用格式

方法引用:類名 ::new

陣列引用:陣列型別 [] :: new

2. 使用要求

2.1 構造器引用

和方法引用類似,函式式介面的抽象方法的形參列表和構造器的形參列表一致。抽象方法的返回值型別即為構造器所屬的類的型別

2.2 陣列引用

可以把陣列看做是一個特殊的類,則寫法與構造器引用一致。

3. 使用舉例

3.1 構造器引用

//構造器引用
//Supplier中的T get()
@Test
public void test1() {
    //使用匿名內部類
    Supplier<Employee> sup = new Supplier<Employee>() {
        @Override
        public Employee get() {
            return new Employee();
        }
    };
    System.out.println(sup.get());
    //使用Lambda表示式
    System.out.println("====================");
    Supplier<Employee> sup1 = () -> new Employee(1001, "Tom", 43, 13333);
    System.out.println(sup1.get());

    //使用方法引用
    Supplier<Employee> sup2 = Employee::new;
    System.out.println(sup2.get());

}

//Function中的R apply(T t)
@Test
public void test2() {
    //使用Lambda表示式
    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)
@Test
public void test3() {
    //使用Lambda表示式
    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, "Jarry"));
}

3.2 陣列引用

//陣列引用
//Function中的R apply(T t)
@Test
public 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));
}

五、StreamAPI

1. Stream API概述

Stream關注的是對資料的運算,與CPU打交道;集合關注的是資料的儲存,與記憶體打交道;
Java8提供了一套api,使用這套api可以對記憶體中的資料進行過濾、排序、對映、歸約等操作。類似於sql對資料庫中表的相關操作。
Stream是資料渠道,用於操作資料來源(集合、陣列等)所生成的元素序列。“集合講的是資料, Stream講的是計算!”
使用注意點:

①Stream 自己不會儲存元素。

②Stream 不會改變源物件。相反,他們會返回一個持有結果的新Stream。

③Stream 操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。

2. Stream使用流程

① Stream的例項化

② 一系列的中間操作(過濾、對映、...)

③ 終止操作

使用流程中的注意點:

一箇中間操作鏈,對資料來源的資料進行處理
一旦執行終止操作,就執行中間操作鏈,併產生結果。之後,不會再被使用

3. 使用方法

3.1 步驟一 建立Stream

3.1.1 建立方式一:通過集合

Java 8的Collection介面被擴充套件,提供了兩個獲取流的方法:

default Stream<E> stream() : 返回一個順序流
default Stream<E> parallelStream() : 返回一個並行流

3.1.2 建立方式二:通過陣列

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

呼叫Arrays類的 static<T> Stream<T> stream(T[] array): 返回一個流
過載形式,能夠處理對應基本型別的陣列:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

3.1.3建立方式三:通過Stream的of()方法

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

public static <T>Stream<T> of(T...values):返回一個流

3.1.4 建立方式四:建立無限流

迭代: public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
生成: public static<T> Stream<T> generate(Supplier<T> s)

程式碼示例:

public class StreamAPITest1 {
    //建立 Stream方式一:通過集合
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();
        //efault Stream<E> stream() : 返回一個順序流
        Stream<Employee> stream = employees.stream();

        //default Stream<E> parallelStream() : 返回一個並行流
        Stream<Employee> employeeStream = employees.parallelStream();
    }

    //建立 Stream方式二:通過陣列
    @Test
    public void test2() {
        int[] arrs = {1, 2, 3, 6, 2};
        //呼叫Arrays類的static <T> Stream<T> stream(T[] array): 返回一個流
        IntStream stream = Arrays.stream(arrs);

        Employee e1 = new Employee(1001, "Tom");
        Employee e2 = new Employee(1002, "Jerry");
        Employee[] employees = {e1, e2};
        Stream<Employee> stream1 = Arrays.stream(employees);
    }

    //建立 Stream方式三:通過Stream的of()
    @Test
    public void test3() {
        Stream<Integer> integerStream = Stream.of(12, 34, 45, 65, 76);
    }

    //建立 Stream方式四:建立無限流
    @Test
    public void test4() {

        //迭代
        //public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
        //遍歷前10個偶數
        Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);

        //生成
        //public static<T> Stream<T> generate(Supplier<T> s)
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }
}

3.2 步驟二 中間操作

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

3.2.1篩選與切片

程式碼示例:

//1-篩選與切片,注意執行終止操作後,Stream流就被關閉了,使用時需要再次建立Stream流
@Test
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();
    //filter(Predicate p)——接收 Lambda , 從流中排除某些元素。
    Stream<Employee> employeeStream = employees.stream();
    //練習:查詢員工表中薪資大於7000的員工資訊
    employeeStream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);

    //limit(n)——截斷流,使其元素不超過給定數量。
    employeeStream.limit(3).forEach(System.out::println);
    System.out.println();

    //skip(n) —— 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補
    employeeStream.skip(3).forEach(System.out::println);
    //distinct()——篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素
    employees.add(new Employee(1010,"劉慶東",56,8000));
    employees.add(new Employee(1010,"劉慶東",56,8000));
    employees.add(new Employee(1010,"劉慶東",56,8000));
    employees.add(new Employee(1010,"劉慶東",56,8000));

    employeeStream.distinct().forEach(System.out::println);
}

3.2.2 對映

程式碼示例:

//2-對映
@Test
public void test2(){
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    //map(Function f)——接收一個函式作為引數,將元素轉換成其他形式或提取資訊,該函式會被應用到每個元素上,並將其對映成一個新的元素。
    list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);

    //練習1:獲取員工姓名長度大於3的員工的姓名。
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<String> nameStream = employees.stream().map(Employee::getName);
    nameStream.filter(name -> name.length() >3).forEach(System.out::println);
    System.out.println();
    //練習2:使用map()中間操作實現flatMap()中間操作方法
    Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::fromStringToStream);
    streamStream.forEach(s ->{
        s.forEach(System.out::println);
    });
    System.out.println();
    //flatMap(Function f)——接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流。
    Stream<Character> characterStream = list.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();
}
//map()和flatMap()方法類似於List中的add()和addAll()方法
@Test
public void test(){
    ArrayList<Object> list1 = new ArrayList<>();
    list1.add(1);
    list1.add(2);
    list1.add(3);
    list1.add(4);

    ArrayList<Object> list2 = new ArrayList<>();
    list2.add(5);
    list2.add(6);
    list2.add(7);
    list2.add(8);

    list1.add(list2);
    System.out.println(list1);//[1, 2, 3, 4, [5, 6, 7, 8]]
    list1.addAll(list2);
    System.out.println(list1);//[1, 2, 3, 4, [5, 6, 7, 8], 5, 6, 7, 8]

}

3.2.3 排序

程式碼示例:

//3-排序
@Test
public void test3(){
    //sorted()——自然排序
    List<Integer> list = Arrays.asList(12, 34, 54, 65, 32);
    list.stream().sorted().forEach(System.out::println);

    //拋異常,原因:Employee沒有實現Comparable介面
    List<Employee> employees = EmployeeData.getEmployees();
    employees.stream().sorted().forEach(System.out::println);

    //sorted(Comparator com)——定製排序
    List<Employee> employees1 = EmployeeData.getEmployees();
    employees1.stream().sorted((e1,e2)->{
        int ageValue = Integer.compare(e1.getAge(), e2.getAge());
        if (ageValue != 0){
            return ageValue;
        }else {
            return -Double.compare(e1.getSalary(),e2.getSalary());
        }

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

3.3 步驟三終止操作

終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、 Integer,甚至是void

流進行了終止操作後,不能再次使用。

3.3.1 匹配與查詢

程式碼示例:

//1-匹配與查詢
@Test
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();

    //allMatch(Predicate p)——檢查是否匹配所有元素。
    //練習:是否所有的員工的年齡都大於18
    boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
    System.out.println(allMatch);
    //anyMatch(Predicate p)——檢查是否至少匹配一個元素。
    //練習:是否存在員工的工資大於 5000
    boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 5000);
    System.out.println(anyMatch);

    //noneMatch(Predicate p)——檢查是否沒有匹配的元素。
    //練習:是否存在員工姓“雷”
    boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
    System.out.println(noneMatch);

    //findFirst——返回第一個元素
    Optional<Employee> first = employees.stream().findFirst();
    System.out.println(first);

    //findAny——返回當前流中的任意元素
    Optional<Employee> employee = employees.parallelStream().findAny();
    System.out.println(employee);


}

@Test
public void test2(){
    List<Employee> employees = EmployeeData.getEmployees();
    // count——返回流中元素的總個數
    long count = employees.stream().filter(e -> e.getSalary()>5000).count();
    System.out.println(count);

    //max(Comparator c)——返回流中最大值
    //練習:返回最高的工資
    Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
    Optional<Double> maxSalary = salaryStream.max(Double::compareTo);
    System.out.println(maxSalary);

    //min(Comparator c)——返回流中最小值
    //練習:返回最低工資的員工
    Optional<Double> minSalary = employees.stream().map(e -> e.getSalary()).min(Double::compareTo);
    System.out.println(minSalary);

    //forEach(Consumer c)——內部迭代
    employees.stream().forEach(System.out::println);
    System.out.println();
    //使用集合的遍歷操作
    employees.forEach(System.out::println);

}

3.3.2 歸約

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

程式碼示例:

//2-歸約
@Test
public void test3(){
    //reduce(T identity, BinaryOperator)——可以將流中元素反覆結合起來,得到一個值。返回 T
    //練習1:計算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);

    //reduce(BinaryOperator) ——可以將流中元素反覆結合起來,得到一個值。返回 Optional<T>
    //練習2:計算公司所有員工工資的總和
    List<Employee> employees = EmployeeData.getEmployees();
    Optional<Double> sumSalary = employees.stream().map(e -> e.getSalary()).reduce(Double::sum);
    System.out.println(sumSalary);

}

3.3.3 收集

Collector介面中方法的實現決定了如何對流執行收集的操作(如收集到List、Set、Map)

Collectors實用類提供了很多靜態方法,可以方便地建立常見收集器例項具體方法與例項如下表:

程式碼示例:

//3-收集
@Test
public void test4(){
    //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);
}

六、Optional類的使用

1. OPtional類的概述

為了解決java中的空指標問題而生!
Optional 類(java.util.Optional) 是一個容器類,它可以儲存型別T的值,代表這個值存在。或者僅僅儲存null,表示這個值不存在。原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。並且可以避免空指標異常。

2. Optional類提供的方法

Optional類提供了很多方法,可以不用再現實的進行空值檢驗。

2.1 建立Optional類物件的方法

Optional.of(T t) : 建立一個 Optional 例項,t必須非空;
Optional.empty() : 建立一個空的 Optional 例項
Optional.ofNullable(T t):t可以為null

2.2 判斷Optional容器是否包含物件

boolean isPresent():判斷是否包含物件

void ifPresent(Consumer<? super T> consumer):如果有值,就執行Consumer介面的實現程式碼,並且該值會作為引數傳給它。

2.3 獲取Optional容器的物件

T get():如果呼叫物件包含值,返回該值,否則拋異常

T orElse(T other):如果有值則將其返回,否則返回指定的other物件

T orElseGet(Supplier<? extends t> other):如果有值則將其返回,否則返回由Supplier介面實現提供的物件。

T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值則將其返回,否則丟擲由Supplier介面實現提供的異常。

2.4 搭配使用

of() 和 get() 方法搭配使用,明確物件非空
ofNullable() 和 orElse() 搭配使用,不確定物件非空

3. 應用舉例

public class OptionalTest {
    @Test
    public void test1() {
        //empty():建立的Optional物件內部的value = null
        Optional<Object> op1 = Optional.empty();
        if (!op1.isPresent()){//Optional封裝的資料是否包含資料
            System.out.println("資料為空");
        }
        System.out.println(op1);
        System.out.println(op1.isPresent());

        //如果Optional封裝的資料value為空,則get()報錯。否則,value不為空時,返回value.
        System.out.println(op1.get());
    }
    @Test
    public void test2(){
        String str = "hello";
//        str = null;
        //of(T t):封裝資料t生成Optional物件。要求t非空,否則報錯。
        Optional<String> op1 = Optional.of(str);
        //get()通常與of()方法搭配使用。用於獲取內部的封裝的資料value
        String str1 = op1.get();
        System.out.println(str1);
    }
    @Test
    public void test3(){
        String str ="Beijing";
        str = null;
        //ofNullable(T t) :封裝資料t賦給Optional內部的value。不要求t非空
        Optional<String> op1 = Optional.ofNullable(str);
        System.out.println(op1);
        //orElse(T t1):如果Optional內部的value非空,則返回此value值。如果
        //value為空,則返回t1.
        String str2 = op1.orElse("shanghai");
        System.out.println(str2);
    }
}

使用Optional類避免產生空指標異常

public class GirlBoyOptionalTest {

    //使用原始方法進行非空檢驗
    public String getGrilName1(Boy boy){
        if (boy != null){
            Girl girl = boy.getGirl();
            if (girl != null){
                return girl.getName();
            }
        }
        return null;
    }
    //使用Optional類的getGirlName()進行非空檢驗
    public String getGirlName2(Boy boy){
        Optional<Boy> boyOptional = Optional.ofNullable(boy);
        //此時的boy1一定非空,boy為空是返回“迪麗熱巴”
        Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪麗熱巴")));

        Girl girl = boy1.getGirl();
        //girl1一定非空,girl為空時返回“古力娜扎”
        Optional<Girl> girlOptional = Optional.ofNullable(girl);
        Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));

        return girl1.getName();
    }

    //測試手動寫的控制檢測
    @Test
    public void test1(){

        Boy boy = null;
        System.out.println(getGrilName1(boy));

        boy = new Boy();
        System.out.println(getGrilName1(boy));

        boy = new Boy(new Girl("楊冪"));
        System.out.println(getGrilName1(boy));
    }
    //測試用Optional類寫的控制檢測
    @Test
    public void test2(){
        Boy boy = null;
        System.out.println(getGirlName2(boy));

        boy = new Boy();
        System.out.println(getGirlName2(boy));

        boy = new Boy(new Girl("楊冪"));
        System.out.println(getGirlName2(boy));

    }
}

七、對反射的支援增強

提高了建立物件、物件賦值和反射建立物件的時間

程式碼示例:

public class testReflection {
    // 迴圈次數10億次
    private static final int loopCnt = 1000 * 1000 * 1000;

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        // 輸出jdk版本
        System.out.println("java version is" + System.getProperty("java.version"));
        creatNewObject();
        optionObject();
        reflectCreatObject();
    }

    // person物件
    static class Person {
        private Integer age = 20;

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }

    // 每次建立新物件
    public static void creatNewObject() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < loopCnt; i++) {
            Person person = new Person();
            person.setAge(30);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("迴圈十億次建立物件所需的時間:" + (endTime - startTime));
    }

    // 為同一個物件賦值
    public static void optionObject() {
        long startTime = System.currentTimeMillis();
        Person p = new Person();
        for (int i = 0; i < loopCnt; i++) {
            p.setAge(10);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("迴圈十億次為同一物件賦值所需的時間:" + (endTime - startTime));
    }

    // 通過反射建立物件
    public static void reflectCreatObject() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        long startTime = System.currentTimeMillis();
        Class<Person> personClass = Person.class;
        Person person = personClass.newInstance();
        Method setAge = personClass.getMethod("setAge", Integer.class);
        for (int i = 0; i < loopCnt; i++) {
            setAge.invoke(person, 90);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("迴圈十億次反射建立物件所需的時間:" + (endTime - startTime));
    }
}

編譯級別為JDK8時

java version is 1.8.0_201
迴圈十億次建立物件所需的時間:9
迴圈十億次為同一物件賦值所需的時間:59
迴圈十億次反射建立物件所需的時間:2622

編譯級別為JDK7時

java version is 1.7
迴圈十億次建立物件所需的時間:6737
迴圈十億次為同一物件賦值所需的時間:3394
迴圈十億次反射建立物件所需的時間:293603