Java 8 新特性——實踐篇

卡斯特梅的雨傘發表於2020-12-15

Java 8 新特性——實踐篇

參考

Java8新特性

重要更新:Lambda 表示式和Stream API

Lambda 表示式

Lambda 表示式引入之前:

舉個場景例子:當我們要對一個班級裡的學生物件裡各種成績進行過濾時,比如大於85分獲得A的學生集合,最初的方式是寫不同的方法處理不同的科目成績過濾;再後面就可以用策略模式,宣告一個介面ScoreFilterStrategy,針對不同的科目實現不同的策略演算法。再優化一下,我們可以對策略模式進行升級,直接用匿名內部類實現我們的介面ScoreFilterStrategy,自定義策略實現。但基於其程式碼的繁瑣性,我們可以使用Lambda 表示式進行函數語言程式設計優化,更可以對集合進行Stream API流的呼叫處理來實現想要的效果。參考如下:

image.png

Lambda 是一個匿名函式,我們可以把Lambda 表示式理解為是一段可以傳遞的程式碼(將程式碼像資料一樣進行傳遞)。

Lambda 表示式語法

Lambda 表示式在Java 語言中引入了一個新的語法元素和操作符。這個操作符為“->” ,該操作符被稱為Lambda 操作符或剪頭操作符。它將Lambda 分為兩個部分:
左側:指定了Lambda 表示式需要的所有引數
右側:指定了Lambda 體,即Lambda 表示式要執行的功能

Lambda 表示式需要函式式介面的支援。

示例:

public class LambdaTest {

    //語法格式一:無參,無返回值,Lambda 體只需一條語句
    @Test
    public void test1() {
        Runnable r = () -> System.out.println("helo world");
        r.run();
    }

    //語法格式二:Lambda 需要一個引數
    @Test
    public void test2() {
        Consumer<String> consumer = (str) -> System.out.println(str);
        consumer.accept("I am Batman");
    }


    //語法格式三:Lambda 只需要一個引數時,引數的小括號可以省略
    @Test
    public void test3() {
        Consumer<String> consumer = str -> System.out.println(str);
        consumer.accept("I am Batman");
    }

    //語法格式四:Lambda 需要兩個引數,並且有返回值,並且lambda體中有多條語句時要加{}
    @Test
    public void test4() {
        Comparator<Integer> comparator = (x, y) -> {
            System.out.println("比較資料");
            return Integer.compare(x, y);
        };
        comparator.compare(1, 2);
    }


    //語法格式五:當Lambda 體只有一條語句時,return 與大括號可以省略
    @Test
    public void test5() {
        Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
        comparator.compare(1, 2);
    }

    //語法格式六:Lambda 表示式的引數列表上的引數型別可以省略不寫,因為JVM編譯器(javac)會通過程式上下文推動出資料型別,
    // 即型別推斷。這是JDK1.8的新特性,在JDK1.7上寫型別推斷的程式碼是編譯不通過的。
    @Test
    public void test6() {
        Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
        comparator.compare(1, 2);
    }

    //型別推斷例項
    @Test
    public void test7() {
        String[] strings = {"a", "b", "ddd"};
        //如果像下面這樣寫就無法通過上下文進行型別推斷
//        String[] string2;
//        string2 = {"a", "b", "ddd"};
        //後面的new ArrayList<>();尖括號裡面也可以省略不寫也是通過型別推斷得來的。
        List<String> list = new ArrayList<>();
        //建立的new HashMap<>()尖括號裡面也可以省略不寫也是通過型別推斷得來的,他會採用operate方法裡的泛型格式
        operate(new HashMap<>());
    }

    private void operate(Map<String,String> map){

    }

}

函式式介面

介面中只包含一個抽象方法的介面,稱為函式式介面。我們可以使用@FunctionalInterface註解修飾該介面,用於檢測是否是函式式介面。

示例:

@FunctionalInterfacepublic interface MyFunc {    Integer getValue(Integer t);}
@FunctionalInterfacepublic interface CustomFunc {    String execute(String str);}
@FunctionalInterface
public interface CauclateFunc<T,R> {

    R getValue(T t1,T t2);
}
public class LambdaTest2 {

    @Test
    public void test() {
        System.out.println(operate(100, x -> x * x));
    }

    //函式式介面作為方法引數傳遞,這樣我們便能在呼叫方法時自定義我們的lambda函式的操作
    private Integer operate(Integer t, MyFunc myFunc) {
        return myFunc.getValue(t);
    }

    @Test
    public void test2() {
        System.out.println(concate("hello",str -> str + " world"));
        String concate = concate("I", str -> {
            System.out.println("prepare...");
            return str + " am " + "batman";
        });
        System.out.println(concate);
    }

    private String concate(String str,CustomFunc customFunc){
        return customFunc.execute(str);
    }
    
    @Test
    public void test3() {
        Long calcute = calcute(100L, 200L, (x, y) -> x * y);
        System.out.println(calcute);
        Long calcute2 = calcute(100L, 200L, (x, y) -> x - y);
        System.out.println(calcute2);
    }

    private Long calcute(Long l1, Long l2, CauclateFunc<Long, Long> cauclateFunc) {
        return cauclateFunc.getValue(l1, l2);
    }
}
輸出:
hello world
prepare...
I am batman

20000
-100

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

image.png

其他子介面是在四大核心函式式介面的基礎上進行個性化的引數新增和處理定義:

image.png

四大核心函式式介面使用示例:

public class FunctionalInterfaceTest {
    /**
     * Consumer<T> :消費型介面:接收一個引數進行消費處理,無返回
     * void accept(T t)
     */
    @Test
    public void test() {
        call("麻包鍋", name -> System.out.println(name + "被老師點名了!"));
    }
    //麻包鍋被老師點名了!

    private void call(String name, Consumer<String> consumer) {
        consumer.accept(name);
    }

    /**
     * Supplier<T> :供給型介面 :返回一些東西
     * T get();
     */
    @Test
    public void test2() {
        //注意加括號,int強轉是針對整個只而不是Math.random()返回的,否則為0
        List<Integer> list = supply(5, () -> (int) (Math.random() * 100));
        System.out.println(list);
    }
    //[78, 96, 66, 20, 0]

    private List<Integer> supply(Integer num, Supplier<Integer> supplier) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
             list.add(supplier.get());
        }
        return list;
    }

    /**
     * Function<T, R>:函式型介面 :對T型別引數物件處理返回R型別物件
     * R apply(T t);
     */
    @Test
    public void test3() {
        String concate = concate("希望耗子尾汁!", str -> "老同志," + str);
        System.out.println(concate);
        String filterStr = filterStr("偷襲,   騙我老同志! ", s -> s.replace(" ",""));
        System.out.println(filterStr);
    }
    //老同志,希望耗子尾汁!
    //偷襲,騙我老同志!

    private String concate(String str, Function<String,String> function) {
        return function.apply(str);
    }

    private String filterStr(String str, Function<String,String> function) {
        return function.apply(str);
    }


    /**
     * Predicate<T> :斷言型介面
     * boolean test(T t);
     */
    @Test
    public void test4() {
        List<Integer> numList = new ArrayList<>();
        numList.add(1);
        numList.add(13);
        numList.add(23);
        numList.add(67);
        List<Integer> list = check(numList, n -> n > 15);
        System.out.println(list);
    }
    //[23, 67]

    List<Integer> check(List<Integer> list,Predicate<Integer> predicate){
        List<Integer> newList = new ArrayList<>();
        for (Integer num : list) {
           if (predicate.test(num)){
               newList.add(num);
           }
        }
        return newList;
    }
}

方法引用與構造器引用

方法引用

當要傳遞給Lambda體的操作,如果Lambda 體中的內容有方法實現了,就可以使用“方法引用”。即方法引用是lambda表示式的另一種表現形式。

有以下三種語法形式:

  • 物件::例項方法
  • 類::靜態方法
  • 類::例項方法

注意:

  • lambda體中呼叫方法的引數列表和返回值型別,要與函式式介面中的抽象方法的引數列表和返回值型別保持一致。
  • 如果lambda引數列表中的第一個引數是示例方法的呼叫者,第二個引數是示例方法的引數時,可以使用ClassName::methodName。

構造器引用

格式:ClassName::new

構造器引用與函式式介面相結合,自動與函式式介面中方法相容。

注意:

  • 當我們把構造器引用賦值給定義的方法時,需要呼叫的構造器的引數列表要與函式式介面中抽象方法的引數列表一致!

陣列引用

格式:type[] :: new

示例:

public class MethodRefTest {
    /**
     * 物件::例項方法
     * 注意方法引用使用時:lamda表示式需要實現的抽象方法的引數型別和返回值要與當前呼叫的方法的引數型別和返回值保持一致
     * Consumer<T>  : void accept(T t);
     * PrintStream : void println(String x);
     * 當呼叫方法有一個引數無返回值時適用於Consumer的消費型介面
     */
    @Test
    public void test() {
        Consumer<String> consumer = System.out::println;
        consumer.accept("朋友們好啊!");
    }

    /**
     * Supplier<T> T get();
     * 呼叫方法無引數有返回值時適用於Supplier的供給型介面
     * 典型的如物件的get方法
     */
    @Test
    public void test2() {
        User user = new User();
        Supplier<String> supplier = user::getName;
        System.out.println(supplier.get());
    }

    /**
     * 類::靜態方法
     * Comparator<T> : int compare(T o1, T o2);
     * Integer : static int compare(int x, int y)
     */
    @Test
    public void test3() {
        List<Integer> numList = new ArrayList<>();
        numList.add(111);
        numList.add(13);
        numList.add(2);
        numList.add(67);
//        Comparator<Integer> comparator = Integer::compare;
//        Collections.sort(numList,comparator);
        Collections.sort(numList, Integer::compare);
        System.out.println(numList);
    }
    //[2, 13, 67, 111]


    /**
     * 類::例項方法
     * 使用條件:如果lambda引數列表中的第一個引數是示例方法的呼叫者,第二個引數是示例方法的引數時
     * BiPredicate<T, U> : boolean test(T t, U u);
     * String : boolean equals(Object anObject)
     */
    @Test
    public void test4() {
        boolean isEqual = checkEqual("music", "Music", String::equals);
        System.out.println(isEqual);
    }
    //false

    private boolean checkEqual(String a, String b, BiPredicate<String, String> biPredicate) {
        return biPredicate.test(a, b);
    }

    /**
     * 構造器引用
     * 需要呼叫的構造器的引數列表要與函式式介面中抽象方法的引數列表一致
     * Supplier<T> : T get(); 對於無參構造器
     * Function<T, R> : R apply(T t); 對於一個引數構造器
     * BiFunction<T, U, R> :  R apply(T t, U u); 對於兩個引數構造器
     */
    @Test
    public void test5() {
        //函數語言程式設計寫法
        Supplier<User> supplier1 = () -> new User();
        //構造器引用
        Supplier<User> supplier = User::new;
        //一個引數構造器
        Function<String, User> function = User::new;
        User user = function.apply("年輕人");
        System.out.println(user);//User{id=0, age=0, name='年輕人'}
        BiFunction<Integer, String, User> biFunction = User::new;
        User user1 = biFunction.apply(69, "老同志");
        System.out.println(user1);//User{id=0, age=69, name='老同志'}
    }

    /**
     * 陣列引用
     */
    @Test
    public void test6() {
        Function<Integer, String[]> function1 = x -> new String[x];
        Function<Integer, String[]> function = String[]::new;
        String[] strings = function.apply(5);
        System.out.println(strings.length);//5
    }

}
public class User {

    private int id;

    private int age;

    private String name;

    public User() {
    }

    public User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public User(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

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

Stream API

Stream 是Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作。使用Stream API 對集合資料進行操作,就類似於使用SQL 執行的資料庫查詢。也可以使用Stream API 來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用的處理資料的方式

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

注意:

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

Stream 的操作有三個步驟:

  • 建立Stream
    一個資料來源(如:集合、陣列),獲取一個流
  • 中間操作
    一箇中間操作鏈,對資料來源的資料進行處理
  • 終止操作
    一個終止操作,執行中間操作鏈,併產生結果

image.png

建立Stream

建立Stream流的4種方式示例:

public class StreamTest {

    //建立Stream流的4種方式
    @Test
    public void test() {
        //1、集合建立流:通過Collection系列集合提供的stream()或parallelStream()方法建立流
        List<String> list = new ArrayList<>();
        //序列流
        Stream<String> stream = list.stream();
        //並行流
        Stream<String> parallelStream = list.parallelStream();

        //2、陣列建立流:通過Arrays.stream(T[] array)靜態方法獲取陣列流
        User[] users = new User[5];
        Stream<User> userStream = Arrays.stream(users);

        //3、Stream建立流: 通過Stream.of(T... values)靜態方法建立流
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 66, 75);

        //4、建立無限流
        // A:通過Stream.iterate(final T seed, final UnaryOperator<T> f)傳入seed和一元函式式介面實現無限流
        Stream<Integer> stream1 = Stream.iterate(1, x -> x + 1);
        stream1.limit(5).forEach(System.out::println);

        // B:通過Stream.igenerate(Supplier<T> s) 提供一個供給型介面實現無限流
        Stream<Double> stream2 = Stream.generate(()->Math.random());
        stream2.limit(5).forEach(System.out::println);

    }
}

輸出:

1
2
3
4
5
0.19737667335799347
0.4379300542517345
0.626269580987739
0.8557261379085842
0.09320455087266999

中間操作

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

image.png

image.png

image.png

中間操作示例:

public class StreamTest2 {
    List<User> users = Arrays.asList(new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
            new User(2, 47, "池寒楓", Arrays.asList("shanghai", "beijing")),
            new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
            new User(5, 380, "霍恩斯", Arrays.asList("shanghai", "beijing")),
            new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")),
            new User(5, 320, "霍恩斯1", Arrays.asList("shanghai", "beijing")),
            new User(5, 600, "霍恩斯2", Arrays.asList("guangzhou", "shengzhen")));

    //中間操作
    @Test
    public void test() {
        //filter: boolean test(T t); 通過斷言型介面引數過濾
        //forEach 是內部迭代,由Stream api 完成,相對的外部迭代就是我們自己寫迭代程式碼iterator
        users.stream().filter(user -> user.getAge() > 23).forEach(System.out::println);
    }

    @Test
    public void test2() {
        //limit 截斷流,擷取maxSize個,當擷取到足夠的個數後便會短路,不再迭代下去
        users.stream().filter(user -> user.getAge() > 23)
                .limit(2).forEach(System.out::println);
    }

    /**
     * User{id=1, age=27, name='大青山'}
     * User{id=2, age=47, name='池寒楓'}
     */

    @Test
    public void test3() {
        //skip 跳過n個
        //distinct 去重 如果是物件需要重寫hashCode和equal方法
        users.stream().filter(user -> user.getAge() > 23)
                .skip(2).distinct().forEach(System.out::println);
    }

    /**
     * User{id=3, age=25, name='艾米'}
     * User{id=5, age=500, name='霍恩斯'}
     */

    @Test
    public void test4() {
        //map 對映 引數是函式式介面Function<? super T, ? extends R>
        users.stream().map(user -> user.getName()).forEach(System.out::println);
    }

    /**
     * 大青山
     * 池寒楓
     * 艾米
     * 池傲天
     * 霍恩斯
     * 霍恩斯
     */

    @Test
    public void test5() {
        //flatMap 對映成流,將流中的每個值都對映成流,把所有的流連線成一個流。
        // 引數是函式式介面Function<? super T, ? extends Stream<? extends R>> mapper ,注意出參必須是個Stream流物件。因此lambda表示式的返回值要是個流
        Stream<String> stream = users.stream().flatMap(user -> user.getAddress().stream());
//        List<String> collect = stream.collect(Collectors.toList());
//        System.out.println(collect);
        stream.forEach(System.out::println);
    }

    @Test
    public void test6() {
        //sorted() 排序:沒有傳引數預設自然排序:  Comparable :注意,要排序的物件必須實現Comparable才能呼叫自然排序sorted()
        /**像String就能呼叫sorted() 排序,因為他實現了Comparable。
         * 否則會報錯:java.lang.ClassCastException: com.self.practice.lambda.User cannot be cast to java.lang.Comparable
         * public final class String
         *     implements java.io.Serializable, Comparable<String>, CharSequence
         */
        //sorted(Comparator<? super T> comparator); 定製排序 Comparator
        Stream<User> stream = users.stream().sorted((e1, e2) -> {
            if (e1.getName().equals(e2.getName())) {
                return e1.getAge().compareTo(e2.getAge());//倒序取反就行,加個負號 -
            } else {
                return e1.getName().compareTo(e2.getName());
            }
        });
        stream.forEach(System.out::println);
    }
}
/**
     * User{id=1, age=27, name='大青山'}
     * User{id=4, age=18, name='池傲天'}
     * User{id=2, age=47, name='池寒楓'}
     * User{id=3, age=25, name='艾米'}
     * User{id=5, age=380, name='霍恩斯'}
     * User{id=5, age=500, name='霍恩斯'}
     * User{id=5, age=320, name='霍恩斯1'}
     * User{id=5, age=600, name='霍恩斯2'}
     */

終止操作

image.png
image.png
image.png

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

image.png

image.png

示例:

public class StreamTest {

    //建立Stream流的4種方式
    @Test
    public void test() {
        //1、集合建立流:通過Collection系列集合提供的stream()或parallelStream()方法建立流
        List<String> list = new ArrayList<>();
        //序列流
        Stream<String> stream = list.stream();
        //並行流
        Stream<String> parallelStream = list.parallelStream();

        //2、陣列建立流:通過Arrays.stream(T[] array)靜態方法獲取陣列流
        User[] users = new User[5];
        Stream<User> userStream = Arrays.stream(users);

        //3、Stream建立流: 通過Stream.of(T... values)靜態方法建立流
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 66, 75);

        //4、建立無限流
        // A:通過Stream.iterate(final T seed, final UnaryOperator<T> f)傳入seed和一元函式式介面實現無限流
        Stream<Integer> stream1 = Stream.iterate(1, x -> x + 1);
        stream1.limit(5).forEach(System.out::println);

        // B:通過Stream.igenerate(Supplier<T> s) 提供一個供給型介面實現無限流
        Stream<Double> stream2 = Stream.generate(()->Math.random());
        stream2.limit(5).forEach(System.out::println);

    }
}

public class StreamTest2 {
    List<User> users = Arrays.asList(new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
            new User(2, 47, "池寒楓", Arrays.asList("shanghai", "beijing")),
            new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
            new User(5, 380, "霍恩斯", Arrays.asList("shanghai", "beijing")),
            new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")),
            new User(5, 320, "霍恩斯1", Arrays.asList("shanghai", "beijing")),
            new User(5, 600, "霍恩斯2", Arrays.asList("guangzhou", "shengzhen")));

    //中間操作
    @Test
    public void test() {
        //filter: boolean test(T t); 通過斷言型介面引數過濾
        //forEach 是內部迭代,由Stream api 完成,相對的外部迭代就是我們自己寫迭代程式碼iterator
        users.stream().filter(user -> user.getAge() > 23).forEach(System.out::println);
    }

    @Test
    public void test2() {
        //limit 截斷流,擷取maxSize個,當擷取到足夠的個數後便會短路,不再迭代下去
        users.stream().filter(user -> user.getAge() > 23)
                .limit(2).forEach(System.out::println);
    }

    /**
     * User{id=1, age=27, name='大青山'}
     * User{id=2, age=47, name='池寒楓'}
     */

    @Test
    public void test3() {
        //skip 跳過n個
        //distinct 去重 如果是物件需要重寫hashCode和equal方法
        users.stream().filter(user -> user.getAge() > 23)
                .skip(2).distinct().forEach(System.out::println);
    }

    /**
     * User{id=3, age=25, name='艾米'}
     * User{id=5, age=500, name='霍恩斯'}
     */

    @Test
    public void test4() {
        //map 對映 引數是函式式介面Function<? super T, ? extends R>
        users.stream().map(user -> user.getName()).forEach(System.out::println);
    }

    /**
     * 大青山
     * 池寒楓
     * 艾米
     * 池傲天
     * 霍恩斯
     * 霍恩斯
     */

    @Test
    public void test5() {
        //flatMap 對映成流,將流中的每個值都對映成流,把所有的流連線成一個流。
        // 引數是函式式介面Function<? super T, ? extends Stream<? extends R>> mapper ,注意出參必須是個Stream流物件。因此lambda表示式的返回值要是個流
        Stream<String> stream = users.stream().flatMap(user -> user.getAddress().stream());
//        List<String> collect = stream.collect(Collectors.toList());
//        System.out.println(collect);
        stream.forEach(System.out::println);
    }

    @Test
    public void test6() {
        //sorted() 排序:沒有傳引數預設自然排序:  Comparable :注意,要排序的物件必須實現Comparable才能呼叫自然排序sorted()
        /**像String就能呼叫sorted() 排序,因為他實現了Comparable。
         * 否則會報錯:java.lang.ClassCastException: com.self.practice.lambda.User cannot be cast to java.lang.Comparable
         * public final class String
         *     implements java.io.Serializable, Comparable<String>, CharSequence
         */
        //sorted(Comparator<? super T> comparator); 定製排序 Comparator
        Stream<User> stream = users.stream().sorted((e1, e2) -> {
            if (e1.getName().equals(e2.getName())) {
                return e1.getAge().compareTo(e2.getAge());//倒序取反就行,加個負號 -
            } else {
                return e1.getName().compareTo(e2.getName());
            }
        });
        stream.forEach(System.out::println);
    }
    /**
     * User{id=1, age=27, name='大青山'}
     * User{id=4, age=18, name='池傲天'}
     * User{id=2, age=47, name='池寒楓'}
     * User{id=3, age=25, name='艾米'}
     * User{id=5, age=380, name='霍恩斯'}
     * User{id=5, age=500, name='霍恩斯'}
     * User{id=5, age=320, name='霍恩斯1'}
     * User{id=5, age=600, name='霍恩斯2'}
     */
}

public class StreamTest3 {
    List<User> users = Arrays.asList(new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
            new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
            new User(2, 47, "池寒楓", Arrays.asList("shanghai", "beijing")),
//            new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
//            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
//            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
//            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
//            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
//            new User(5, 380, "霍恩斯", Arrays.asList("shanghai", "beijing")),
            new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")));

    //終止操作
    @Test
    public void test() {
        //anyMatch(Predicate<? super T> predicate) 檢查是否至少匹配一個元素
        boolean b = users.stream().anyMatch(user -> user.getAge() > 600);
        System.out.println(b);//false
        //allMatch(Predicate<? super T> predicate) 檢查是否匹配所有元素
        boolean b1 = users.stream().allMatch(user -> user.getAge() < 600);
        System.out.println(b1);//true
        //noneMatch(Predicate<? super T> predicate) 檢查是否所有元素都沒有匹配到
        boolean b2 = users.stream().noneMatch(user -> "雷葛".equals(user.getName()));
        System.out.println(b2);//true
        //先排序再獲取第一個最大或最小的值
        //Optional<T> findFirst() 返回第一個元素 ,注意因為集合可能為空因此返回了Optional物件
        Optional<User> user = users.stream().sorted(Comparator.comparing(User::getAge)).findFirst();
        user.ifPresent(System.out::println);

        //先過濾出符合條件的隨便找一個
        // Optional<T> findAny() 返回當前流中的任意元素,總感覺不是返回任意一個?而是返回第一個符合條件的
        Optional<User> userOptional = users.stream().filter(u -> u.getAge() < 30).findAny();
        userOptional.ifPresent(System.out::println);

        long count = users.stream().count();
        System.out.println(count);
        //Optional<T> max(Comparator<? super T> comparator) 返回流中最大值
        Optional<User> max = users.stream().max(Comparator.comparing(User::getAge));
        System.out.println(max);
        //Optional<T> min(Comparator<? super T> comparator) 返回流中最小值
        Optional<String> min = users.stream().map(u -> u.getName()).min(String::compareTo);
        System.out.println(min);

    }

    //歸約
    @Test
    public void test1(){
        /**
         * 歸約,可以將流中的元素反覆結合起來,得到一個值。像我們下面可以對各個值進行累加操作,也可以進行累乘等操作
         * identity作為起始值作為x,然後把流中的元素作為y進行操作;得到的結果繼續作為x,繼續對流的元素填充y進行操作
         * T reduce(T identity, BinaryOperator<T> accumulator);
         *BinaryOperator<T> :R apply(T t, U u);
         */
//        Integer totalAge = users.stream().map(user -> user.getAge()).reduce(0, (x, y) -> x + y);
        //get和累加的兩種寫法
//        Integer totalAge = users.stream().map(User::getAge).reduce(0, Integer::sum);
        //如果沒有identity作為起始值,那麼返回的是Optional,因為集合可能為空
        //map-reduce連線稱為map-reduce模式,網路搜尋模式(大資料搜尋)
        Optional<Integer> totalAge = users.stream().map(User::getAge).reduce( Integer::sum);
        System.out.println(totalAge.get());//617

        //歸約拼接字串
        Optional<String> stringOptional = users.stream().map(User::getName).reduce(String::concat);
        System.out.println(stringOptional.get());//艾米大青山池寒楓池傲天霍恩斯
    }

    //收集
    @Test
    public void test2(){
        /**
         * collect將流轉化為其他的形式,接收一個collector實現,用於給stream中元素做彙總的方法
         */
        List<String> list = users.stream().map(User::getName).collect(Collectors.toList());
        list.forEach(System.out::print);//艾米大青山池寒楓池傲天霍恩斯

        //set去重
        Set<String> set = users.stream().map(User::getName).collect(Collectors.toSet());
        set.forEach(System.out::print);//艾米大青山池寒楓池傲天霍恩斯

        //把流中元素收集到建立的集合.如HashSet::new
        HashSet<String> hashSet = users.stream().map(User::getName).collect(Collectors.toCollection(HashSet::new));
        hashSet.forEach(System.out::print);//艾米大青山池寒楓池傲天霍恩斯

        //收集統計
        Long total = users.stream().collect(Collectors.counting());
        System.out.println(total);//5

        //平均值。年齡平均值
        Double avarage = users.stream().collect(Collectors.averagingInt(User::getAge));
        System.out.println(avarage);//123.4

        //總和
        Integer sum = users.stream().collect(Collectors.summingInt(User::getAge));
        System.out.println(sum);//617

        //最大值
//        Optional<User> max = users.stream().max((u1, u2) -> Integer.compare(u1.getAge(), u2.getAge()));
        Optional<User> max = users.stream().collect(Collectors.maxBy((u1, u2) -> Integer.compare(u1.getAge(), u2.getAge())));
        System.out.println(max.get());//User{id=5, age=500, name='霍恩斯'}

        //最小值.注意不是物件就可以用Integer::compare方法引用替換,物件的話還是要寫函式式方法
        Optional<Integer> min = users.stream().map(User::getAge).collect(Collectors.minBy(Integer::compare));
        System.out.println(min.get());//18
    }

    //分組
    @Test
    public void test3(){
        //按名字分組
        Map<String, List<User>> map = users.stream().collect(Collectors.groupingBy(User::getName));
        System.out.println(map);
        //{霍恩斯=[User{id=5, age=500, name='霍恩斯'}], 艾米=[User{id=3, age=25, name='艾米'}], 池傲天=[User{id=4, age=18, name='池傲天'}], 大青山=[User{id=1, age=27, name='大青山'}], 池寒楓=[User{id=2, age=47, name='池寒楓'}]}

        //多級分組
        Map<String, Map<String, List<User>>> collect = users.stream().collect(Collectors.groupingBy(user -> {
            if (user.getAge() < 20) {
                return "young";
            } else if (user.getAge() < 50) {
                return "adult";
            } else {
                return "old";
            }
        }, Collectors.groupingBy(User::getName)));
        System.out.println(collect);
        //{young={池傲天=[User{id=4, age=18, name='池傲天'}]}, old={霍恩斯=[User{id=5, age=500, name='霍恩斯'}]}, adult={艾米=[User{id=3, age=25, name='艾米'}], 大青山=[User{id=1, age=27, name='大青山'}], 池寒楓=[User{id=2, age=47, name='池寒楓'}]}}
    }

    //分割槽,即滿足與不滿足條件的元素區分
    @Test
    public void test4(){
        //年齡大於50的為true,滿足條件
        Map<Boolean, List<User>> map = users.stream().collect(Collectors.partitioningBy(user -> user.getAge() > 50));
        System.out.println(map);
        //{false=[User{id=3, age=25, name='艾米'}, User{id=1, age=27, name='大青山'}, User{id=2, age=47, name='池寒楓'}, User{id=4, age=18, name='池傲天'}], true=[User{id=5, age=500, name='霍恩斯'}]}
    }

    @Test
    public void test5(){
        //獲取統計,收集流中Integer屬性的統計值。
        IntSummaryStatistics statistics = users.stream().collect(Collectors.summarizingInt(User::getAge));
        System.out.println(statistics.getAverage());//123.4
        System.out.println(statistics.getMax());//500
        System.out.println(statistics.getSum());//617
    }

    //連線流中每個字串
    @Test
    public void test6(){
        String str = users.stream().map(User::getName).collect(Collectors.joining());
        //加分隔符,
        String str1 = users.stream().map(User::getName).collect(Collectors.joining("-"));
        //加字首、字尾
        String str2 = users.stream().map(User::getName).collect(Collectors.joining("-","start","end"));
        System.out.println(str);//艾米大青山池寒楓池傲天霍恩斯
        System.out.println(str1);//艾米-大青山-池寒楓-池傲天-霍恩斯
        System.out.println(str2);//start艾米-大青山-池寒楓-池傲天-霍恩斯end

    }
}

public class StreamPractice {
    List<User> users = Arrays.asList(new User(3, 25, "艾米", Arrays.asList("shanghai", "beijing")),
            new User(1, 27, "大青山", Arrays.asList("shanghai", "beijing")),
            new User(2, 47, "池寒楓", Arrays.asList("shanghai", "beijing")),
            new User(4, 18, "池傲天", Arrays.asList("shanghai", "beijing")),
            new User(5, 500, "霍恩斯", Arrays.asList("shanghai", "beijing")));
    @Test
    public void test() {
        List<Integer> list = Arrays.asList(1, 3, 5, 8);
        List<Integer> collect = list.stream().map(e -> e * e).collect(Collectors.toList());
        System.out.println(collect);//[1, 9, 25, 64]
    }

    @Test
    public void test1() {
        Optional<Integer> total = users.stream().map(user -> 1).reduce(Integer::sum);
        System.out.println(total);//5
    }
}

並行流

Fork/Join 框架與傳統執行緒池的區別
採用“工作竊取”模式(work-stealing)
當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加到執行緒佇列中,然後再從一個隨機執行緒的佇列中偷一個並把它放在自己的佇列中。
相對於一般的執行緒池實現,fork/join框架的優勢體現在對其中包含的任務的處理方式上.在一般的執行緒池中,如果一個執行緒正在執行的任務由於某些原因無法繼續執行,那麼該執行緒會處於等待狀態.而在fork/join框架實現中,如果某個子問題由於等待另外一個子問題的完成而無法繼續執行.那麼處理該子問題的執行緒會主動尋找其他尚未執行的子問題來執行.這種方式減少了執行緒的等待時間,提高了效能.

image.png

示例:

    @Test
    public void test2() {
        Instant start = Instant.now();
        OptionalLong aLong = LongStream.rangeClosed(0, 100000000).parallel().reduce(Long::sum);
        System.out.println(aLong);
        Instant end = Instant.now();
        System.out.println("耗時長:" + Duration.between(start, end).toMillis());
    }
    //OptionalLong[5000000050000000]
    //耗時長:55

Optional——容器類

Optional 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用null 表示一個值不存在,現在Optional 可以更好的表達這個概念。並且可以避免空指標異常。

示例:

public class OptionalTest {

    /**
     * 常用方法:
     * Optional.of(T t) : 建立一個Optional 例項
     * Optional.empty() : 建立一個空的Optional 例項
     * Optional.ofNullable(T t):若t 不為null,建立Optional 例項,否則建立空例項
     * isPresent() : 判斷是否包含值
     * orElse(T t) : 如果呼叫物件包含值,返回該值,否則返回t
     * orElseGet(Supplier s) :如果呼叫物件包含值,返回該值,否則返回s 獲取的值
     * map(Function f): 如果有值對其處理,並返回處理後的Optional,否則返回Optional.empty()
     * flatMap(Function mapper):與map 類似,要求返回值必須是Optional
     */

    @Test
    public void test(){
        //Optional.of(T t) : 建立一個Optional 例項
        Optional<User> userOptional = Optional.of(new User());
        User user = userOptional.get();
        System.out.println(user);//User{id=null, age=null, name='null'}
        Optional<User> empty = Optional.empty();
//        User user1 = empty.get();//java.util.NoSuchElementException: No value present
//        System.out.println(user1);
        //Optional.ofNullable(T t):若t 不為null,建立Optional 例項,否則建立空例項
        //是of和empty方法的組合使用
        Optional<User> userOptional2 = Optional.ofNullable(new User());
        //isPresent() : 判斷是否包含值
        if (userOptional2.isPresent()){
            System.out.println(userOptional2.get());
        }
        //ifPresent(Consumer<? super T> consumer) 判斷存在包含值,存在則進行Consumer消費,一般選擇這個處理Optional
        userOptional2.ifPresent(System.out::print);
        //orElse(T t) : 如果呼叫物件包含值,返回該值,否則返回引數值t
        User user1 = empty.orElse(new User());
        //orElseGet(Supplier s) :如果呼叫物件包含值,返回該值,否則呼叫供給型函式獲取值,
        User user2 = empty.orElseGet(User::new);
        //ap(Function f): 如果有值對其處理,並返回處理後的Optional,否則返回Optional.empty()
//        Optional<String> s = userOptional2.map(e -> e.getName());

        //flatMap要求返回值必須是Optional,因此必須對返回值進行Optional.of包裝
        Optional<String> optionalS = userOptional2.flatMap(e -> {
            return Optional.of(e.getName());
        });
    }

    @Test
    public void test1(){
        Optional<User> optional = Optional.ofNullable(null);
        String fanName = getFanName(optional);
        System.out.println(fanName);//null
    }

     //注意方法引數列表同樣可以使用Optional包裝
    public String getFanName(ser){
        //Optional使用Optional封裝屬性值物件及orElse來避免過多的空指標判斷
        return user.orElse(new User())
                .getFan()
                .orElse(new Fan())
                .getName();//null
    }
}

介面中的預設方法與靜態方法

Java 8中允許介面中包含具有具體實現的方法,該方法稱為“預設方法”,預設方法使用default關鍵字修飾。

介面預設方法的”類優先”原則
若一個介面中定義了一個預設方法,而另外一個父類或介面中又定義了一個同名的方法時:

  • 選擇父類中的方法。如果一個父類提供了具體的實現,那麼介面中具有相同名稱和引數的預設方法會被忽略
  • 介面衝突。如果一個父介面提供一個預設方法,而另一個介面也提供了一個具有相同名稱和引數列表的方法(不管方法是否是預設方法),那麼必須覆蓋該方法來解決衝突。覆蓋選擇其中一個介面的預設實現或者自己重寫一個實現。

示例:

public interface IHello {

    default void say(){
        System.out.println("say hello");
    }

    String getName();

    //Java8 中,介面中允許新增靜態方法
    public static void staticMethod(String str){
        System.out.println(str);
    }
}

新時間日期API

LocalDate、LocalTime、LocalDateTime 類的例項是不可變的物件,因此是執行緒安全的,分別表示使用ISO-8601日曆系統的日期、時間、日期和時間。而SimpleDateFormat是可變物件,因此不是執行緒安全的,在使用時要注意每次使用時new建立一個新的SimpleDateFormat物件或者把SimpleDateFormat放在ThreadLocal上。

注:ISO-8601日曆系統是國際標準化組織制定的現代公民的日期和時間的表示法。

Instant 時間戳,用於“時間戳”的運算。它是以Unix元年(傳統的設定為UTC時區1970年1月1日午夜時分)開始所經歷的描述進行運算

Duration:用於計算兩個“時間”間隔
Period:用於計算兩個“日期”間隔

Java8 中加入了對時區的支援,帶時區的時間為分別為:
ZonedDate、ZonedTime、ZonedDateTime參考地址

image.png

image.png

程式碼示例:

public class LocalDateTest {

    //LocalDateTime :本地日期時間 LocalDate LocalTime
    @Test
    public void test(){
        //獲取當前日期時間
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);//2018-12-15T01:40:03.375
        //獲取指定日期時間
        LocalDateTime time = LocalDateTime.of(2018, 7, 10, 10, 10, 59);
        System.out.println(time);//2018-07-10T10:10:59
        //獲取增加一天的日期時間
        LocalDateTime nextDay = now.plusDays(1);
        System.out.println(nextDay);//2018-12-16T01:41:41.071
        //獲取減少2個月的日期時間
        LocalDateTime time1 = now.minusMonths(2);
        System.out.println(time1);//2018-10-15T01:42:43.969
        System.out.println(now.getMonth());//DECEMBER
        System.out.println(now.getDayOfMonth());//15
        System.out.println(now.getMonthValue());//12
        System.out.println(now.getSecond());//45
    }

    //Instant 時間戳
    @Test
    public void test2(){
        //獲取當前時間戳,預設獲取的是UTC時區的時間戳
        Instant now = Instant.now();
        System.out.println(now);//2020-12-14T17:58:20.991Z
        OffsetDateTime dateTime = now.atOffset(ZoneOffset.ofHours(8));
        System.out.println(dateTime);//2020-12-15T02:05:57.558+08:00
        OffsetDateTime dateTime1 = now.atOffset(ZoneOffset.of("+8"));
        System.out.println(dateTime1);//2020-12-15T02:05:57.558+08:00
        //獲取當前時間的時間戳
        long epochMilli = now.toEpochMilli();
        //舊的獲取方式
        long l = System.currentTimeMillis();
        //新老獲取時間戳的值是一樣的,只是新的時間戳返回的時間是UTC時區的日期時間,但在用new Date(l)轉化時或預設轉化為
        //當前日期時間。這個需要注意
        System.out.println(epochMilli);//1607968605927
        System.out.println(l);//1607968605927
        System.out.println(new Date(l));//Tue Dec 15 01:59:01 CST 2020
    }

    //Duration:時間間隔
    @Test
    public void test3(){
        Instant now = Instant.now();
        Instant instant = Instant.ofEpochMilli(1607968605927L);
        //Duration:用於計算兩個“時間”間隔.注意間隔是用第二個引數減去第一個引數的間隔,因此當第一個引數是比較晚時得到的是負數
        Duration duration = Duration.between(instant, now);
        //獲取間隔的毫秒值用to
        System.out.println(duration.toMillis());//992143
        //獲取間隔的秒值用get
        System.out.println(duration.getSeconds());//992

        LocalDateTime localDateTime = LocalDateTime.now();
        LocalDateTime dateTime = LocalDateTime.of(2020, 12, 15, 0, 0, 0);
        Duration between = Duration.between(localDateTime, dateTime);
        System.out.println(between.getSeconds());//-8261
    }

    //Period:日期間隔
    @Test
    public void test5(){
        LocalDate now = LocalDate.now();
        LocalDate date = LocalDate.of(2020, 11, 11);
        //Period:用於計算兩個“日期”間隔 獲取值不好用
        Period period = Period.between(date, now);
        System.out.println(period);//P1M4D:表示間隔1個月04天
        System.out.println(period.getChronology());//ISO 年表
        System.out.println(period.getYears());//0
        System.out.println(period.getMonths());//1
        System.out.println(period.getDays());//4
    }

    //TemporalAdjuster:時間校正器
    @Test
    public void test6(){
        LocalDateTime now = LocalDateTime.now();
        //調整日期為該月的第一天
        LocalDateTime dateTime = now.withDayOfMonth(1);
        System.out.println(dateTime);//2020-12-01T02:39:06.111
        //調整日期為該月的第一天
        LocalDateTime time = now.with(TemporalAdjusters.firstDayOfMonth());
        System.out.println(time);//2020-12-01T02:35:01.715
        //調整日期為下一個星期天
        LocalDateTime nextSunday = now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        System.out.println(nextSunday);//2020-12-20T02:37:01.322

        //自定義時間校正器 :獲取下一個工作日
        LocalDateTime nextWorkDate = now.with(temporal -> {
            LocalDateTime dateTime1 = (LocalDateTime) temporal;
            if (dateTime1.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
                return dateTime1.plusDays(3);
            } else if (dateTime1.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
                return dateTime1.plusDays(2);
            } else {
                return dateTime1.plusDays(1);
            }
        });
        System.out.println(nextWorkDate);//2020-12-16T02:44:45.370
    }

    //DateTimeFormatter :格式化時間/日期
    @Test
    public void test7(){
        DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd 時HH分mm秒ss");
        LocalDateTime now = LocalDateTime.now();
        //日期轉指定格式的字串
        String str = formatter.format(now);
        System.out.println(str);//2020-12-15
        String str2 = formatter2.format(now);
        System.out.println(str2);//2020-12-15 時02分54秒47

        //注意2020-12-15這種型別的字串沒法轉化為日期時間LocalDateTime,只能轉化為日期LocalDate
//        會報錯:java.time.format.DateTimeParseException: Text '2020-12-15' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2020-12-15 of type java.time.format.Parsed
//        LocalDateTime dateTime = now.parse(str, formatter);
        LocalDate date = LocalDate.parse(str, formatter);
        System.out.println(date);//2020-12-15
        //指定格式字串轉日期時間
        LocalDateTime dateTime2 = LocalDateTime.parse(str2, formatter2);
        System.out.println(dateTime2);//2020-12-15T03:01:43

    }

    //帶時區的時間為分別為:ZonedDate、ZonedTime、ZonedDateTime
    //Asia/Shanghai  America/New_York
    @Test
    public void test9(){
        //獲取帶時區的日期和時間 方式一
        //預設時區
        ZonedDateTime now1 = ZonedDateTime.now();
        System.out.println(now1);//2020-12-15T03:34:44.479+08:00[Asia/Shanghai]
        //用指定時區獲取當前時間
        ZonedDateTime newyorkNow = ZonedDateTime.now(ZoneId.of("America/New_York"));
        System.out.println(newyorkNow);//2020-12-14T14:36:21.999-05:00[America/New_York]

        //獲取帶時區的日期和時間 方式二
        //以這種方式建立的ZonedDateTime,它的日期和時間與LocalDateTime相同,但附加的時區不同,因此是兩個不同的時刻
        LocalDateTime now = LocalDateTime.now(ZoneId.of("America/New_York"));
        System.out.println(now.atZone(ZoneId.systemDefault()));//2020-12-14T14:39:04.255+08:00[Asia/Shanghai]

        LocalDateTime now2 = LocalDateTime.now(ZoneId.of("America/New_York"));
        //獲取到的區域時間是當地日期時間 +時區時間差
        ZonedDateTime zonedDateTime = now2.atZone(ZoneId.of("America/New_York"));
        System.out.println(zonedDateTime);//2020-12-14T14:19:41.923-05:00[America/New_York]

        //時區轉換
        //通過withZoneSameInstant()將關聯時區轉換到另一個時區,轉換後日期和時間都會相應調整。
        //時區轉換的時候,由於夏令時的存在,不同的日期轉換的結果很可能是不同的。
        //涉及到時區時,千萬不要自己計算時差,否則難以正確處理夏令時。
        //預設時區
        ZonedDateTime now3 = ZonedDateTime.now();
        ZonedDateTime newYorkDateTime1 = now3.withZoneSameInstant(ZoneId.of("America/New_York"));
        System.out.println(now3);//2020-12-15T03:42:58.940+08:00[Asia/Shanghai]
        System.out.println(newYorkDateTime1);//2020-12-14T14:42:58.940-05:00[America/New_York]
        //紐約時區時間轉換為本地時間
        LocalDateTime localDateTime = newYorkDateTime1.toLocalDateTime();
        System.out.println(localDateTime);//丟棄了時區資訊

        //練習:某航線從北京飛到紐約需要13小時20分鐘,請根據北京起飛日期和時間計算到達紐約的當地日期和時間。
        ZonedDateTime now4 = ZonedDateTime.now();
        ZonedDateTime zonedDateTime1 = now4.plusHours(13).plusMinutes(20);
        ZonedDateTime newyorkDateTime2 = zonedDateTime1.withZoneSameInstant(ZoneId.of("America/New_York"));
        System.out.println(newyorkDateTime2);//2020-12-15T04:12:39.611-05:00[America/New_York]
        System.out.println("before:"+now4);//before:2020-12-15T03:52:39.611+08:00[Asia/Shanghai]
        System.out.println("after:"+zonedDateTime1);//after:2020-12-15T17:12:39.611+08:00[Asia/Shanghai]
    }

    @Test
    public void test8(){
        Set<String> zoneIds = ZoneId.getAvailableZoneIds();
        zoneIds.forEach(System.out::println);
    }
}

重複註解和型別註解

示例:

//定義可重複註解的容器
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NameAnnotions {
    NameAnnotion[] value();
}

//可重複註解必須加@Repeatable(NameAnnotions.class),NameAnnotions為其容器
@Repeatable(NameAnnotions.class)
//注意型別註解要求這兩個目標註解都要標註才能使用,否則報錯:ElementType.PARAMETER,ElementType.TYPE_PARAMETER
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.TYPE,ElementType.PARAMETER,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NameAnnotion {
    String value() default "";
}

public class AnnotionTest {

    @Test
    public void test() throws NoSuchMethodException {
        Class<AnnotionTest> aClass = AnnotionTest.class;
        Method work = aClass.getMethod("work");
        NameAnnotion[] annotations = work.getAnnotationsByType(NameAnnotion.class);
        for (NameAnnotion annotation : annotations) {
            System.out.println(annotation.value());
        }
    }

    //可重複註解
    @NameAnnotion("996")
    @NameAnnotion("955")
    public void work(@NameAnnotion String str) {
    }
}

其他新特性

HashMap的新特性——從陣列加連結串列的儲存方式轉變為陣列加(連結串列/紅黑樹)的方式。

HashMap在儲存資料的時候會先計算出Key的hash值,再通過運算得到我們底層儲存的陣列的下標索引值。 當hash值一樣時,會呼叫equal()方法比較內容是否相等,如果相等則替換原有的值,如果不相等,此時就產生了hash碰撞,資料會被以連結串列的形式存放起來。而當hash碰撞嚴重時,比如極端情況下,連結串列就會很長,導致查詢或插入比較時效率低下,這時候我們就引入了採用紅黑樹來替換連結串列的形式進行儲存。

連結串列轉紅黑樹的條件是:當連結串列的長度大於8且HashMap的容量大於64時會觸發由連結串列轉紅黑樹邏輯。紅黑樹除了新增以外其他的效率都很高,因為連結串列新增的時候是新增在末尾比較快,而紅黑樹新增時要進行比較大小新增。

載入因子設定為0.75的原因,因為如果太小,HashMap就會不斷擴容,浪費效率。太大了可能插入的值就是一直產生碰撞形成了連結串列,沒有插入到陣列的索引位置,導致一直沒有擴容,效率低下。

ConcurrentHashMap的新特性——把鎖分段技術改成CAS演算法

ConcurrentHashMap在JDK1.8之前是採用鎖分段技術,預設由16個分段,對於16把鎖。因為分段的數量不好控制,如果分段過多就會浪費空間,因為很多段裡面其實沒有資料進入,而太小也不好,這樣就導致效率太低,因為會導致太多操作競爭同一個段鎖。

底層也採用陣列加(連結串列/紅黑樹)的方式提高效率。

方法區實現從永久代實現變為元空間MetaSpace。

元空間使用的是實體記憶體。預設實體記憶體有多大,元空間就可以是多大,只受限於實體記憶體的大小,當然我們也可以指定MetaSpaceSize指定元空間大小,MaxMetaSpaceSize指定最大元空間大小。取代了永久代的PermGenSize和MaxPermGenSize.

相關文章