­

Java 8 新特性——實踐篇

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.

Tags: