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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM