jdk1.8 新特性

本文摘抄自網絡,做爲我的學習筆記java

 

知識點:算法

  • jdk1.8 對 hashmap 的優化
  • Lambda 表達式
  • 函數式接口
  • 方法引用和構造器調用
  • Stream API
  • 接口中的默認方法和靜態方法
  • 新時間日期 API

 

 

map 新原理 api

 

  在 jdk1.8 中對 hashMap 等 map 集合的數據結構進行了優化。原來的 hashMap 採用的數據結構是哈希表(數組+鏈表),hashMap 默認大小是 16,一個 0-15 索引的數組。怎麼往裏面存儲元素呢?首先調用元素的 hashcode 方法,計算出 哈希碼值,通過 哈希算法算成數組的索引值,若是對應的索引處沒有元素,直接存放,若是有對象在,那麼比較他們的 equals 方法,若是內容同樣,後一個 value 會將前一個 value 的值覆蓋,若是不同,在 1.7 的時候,後加的放在前面造成一個鏈表,這就形成了「碰撞」,在某些狀況下,若是鏈表無限下去,,那麼效率極低,碰撞是避免不了的。數組

  加載因子:0.75,數組擴容,達到總容量的 75%,就進行擴容,可是沒法避免碰撞的狀況發生。安全

在 1.8 以後,在 數組 + 鏈表 + 紅黑樹 來實現 hashmap,當碰撞的元素大於 8 & 總容量大於 64,會有紅黑樹的引入。除了添加以外,效率都比鏈表高,1.8 以後鏈表新進元素加到末尾。網絡

  ConcurrentHashMap(鎖分段機制),concurrentLevel ,jdk1.8 採用 CAS 算法(無鎖算法,再也不使用鎖分段)。數據結構

  數組 + 鏈表 中也引入了紅黑樹的使用。多線程

 

 

Lambda 表達式app

 

  Lambda 表達式本質上是一段匿名內部類,也能夠是一段傳遞的代碼框架

Comparartor<Integer> cpt = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> set = new TreeSet<>(cpt);

 

 Lambda 表達式的語法總結:() -> ();

 

注:當一個接口中存在多個抽象方法時,若是使用 lambda 表達式,並不能智能地匹配對應的抽象方法,所以引入了函數式接口的概念

 

 

函數式接口

   函數式接口的提出是爲了給 Lambda 表達式的使用提供更好的支持。

 

什麼是函數式接口?

  簡單來講就是隻定義了一個抽象方法的接口(Object 類的 public 方法除外),就是函數式接口,而且還提供了註解:@FunctionalInterface

 

常見的四大函數式接口:

  • Consumer<T>:消費型接口,有參無返回值

    // 消費型

    public static void changeStr(String str, Consumer<String> con){
      con.accept(str);
    }

    // 調用

    changeStr("hello", (str) -> System.out.println(str));

    說明:這裏方法的參數是字符串和 Consumer,Consumer 是接口,調用時須要實現這個接口,即實現了消費這個操做。

  • Supplier<T>:供給型接口,無參有返回值
    // 供給型
    public
    static String getValue(Supplier<String> sup){ return sup.get(); }

    // 調用
    System.out.println(getValue(() -> "1111111"));

    說明:這裏同上,返回的是個字符串,按理有個 return,可是 lambda 表達式說,按照個人規範,能夠省略這個 return。

  • Function<T, R>:函數式接口,有參數有返回值
    // 函數式接口
    public
    static Long changeNum(Long num, Function<Long, Long> function){ return function.apply(num); }
    // 調用

    Long result = changeNum(100L, (x) -> x+200L);

     

  • Predicate<T>:斷言型接口,有參有返回值,返回值是 boolean 類型
    // 斷言型
    public static boolean changeBoolean(String str, Predicate<String> pre){
        return pre.test(str);
    }
    // 調用
    boolean reboolean = changeBoolean("hello", (str)->str.length() > 5);

     

   總結:函數式接口的提出是爲了讓咱們更加方便的使用 lambda 表達式,不須要本身再手動建立一個函數式接口,直接拿來用就行了。

 

 

方法引用

  若 lambda 體中的內容有方法已經實現了,那麼可使用 「方法引用」。也能夠理解爲方法引用是 lambda 表達式的另一種表現形式,而且其語法比 lambda 表達式更加簡單。

 

1)方法引用

  三種表現形式:

  1. 對象 :: 實例方法名

  2. 類 :: 靜態方法名

  3. 類 :: 實例方法名(lambda 參數列表中第一個參數是實例方法的調用者,第二個參數是實例方法的參數時可用)

// 方法引用
/**
 * 1. lambda 體中調用方法的參數列表與返回值類型,
 *      要與函數式接口中抽象方法的參數列表和返回值保持一致
 * 2. 若 lambda 參數列表中的第一個參數是實例方法的調用者,
 *      而第二個參數是實例方法的參數時,可使用 ClassName::method
 */

Consumer<Integer> consumer = (x) -> System.out.println("x");
consumer.accept(100);

// 方法引用 - 類名 :: 靜態方法名
BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);
BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
Integer resBi = biFun2.apply(1200, 200);
System.out.println("resBi = " + resBi);

// 方法引用 - 類名 :: 實例方法名
BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
BiFunction<String, String, Boolean> fun2 = String::equals;
Boolean resB2 = fun2.apply("hello", "hello");
System.out.println("resB2 = " + resB2);

 

 2)構造器引用

  格式:ClassName :: new 

    Supplier<Product> sup = () -> new Product();
    System.out.println("sup.get() = " + sup.get());

    Supplier<Product> sup2 = Product::new;
    System.out.println("sup2.get() = " + sup2.get());

  // 一個參數的狀況 Function
<String, Product> bifun2 = Product::new; BiFunction<Integer, String, Product> bifun = (x, y) -> new Product(y, x);

 

 3)數組引用

  格式:Type[] :: new 

    // 數組引用
    Function<Integer, String[]> func = (x) -> new String[x];
    Function<Integer, String[]> func2 = String[] :: new ;

    String[] strArray = func2.apply(10);
    Arrays.stream(strArray).forEach(System.out::println);

 

 

 Strean API

   Stream 操做的三個步驟

  • 建立 stream
  • 中間操做(過濾、map)
  • 終止操做

  stream 的建立:

    // Stream 操做
    // 1. 經過 Collection 系列集合提供的 stream() 或者 paralleStream()
    List<String> slist = new ArrayList<>();
    Stream<String> stream1 = slist.parallelStream();
    Stream<String> stream11 = slist.stream();

    // 2. 經過 Arrays 的靜態方法 stream() 獲取數組流
    String[] strArr = new String[10];
    Stream<String> stream2 = Arrays.stream(strArr);

    // 3. 經過 Stream 類的靜態方法 of
    Stream<String> stream3 = Stream.of("aa", "nn", "cc");

    // 4. 建立無限流
    // 迭代
    Stream<Integer> stream4 = Stream.iterate(0, (x)->x+2);
    // 生成
    Stream.generate(()->Math.random());

 

  stream 的中間操做(Intermediate):一個流能夠後面跟隨 0 個或者多個 intermediate 操做。其目的主要是打開流,作出某種程度的數據映射 / 過濾,而後返回一個新的流,交給下一個操做者使用。這類操做都是惰性化的(lazy),就是說,僅僅調用到這類方法,並無真正開始流的遍歷。

    // 篩選、過濾、去重
    list.stream()
            .filter(e -> e.getPrice()>20)
            .limit(3)
            .skip(1)
            .forEach(System.out::println);

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

    // 經過 map 映射,生成新的流
    list.stream()
            .map(e -> {if(e.getPrice()>20){return e;}else{return null;}})
            .forEach(System.out::println);

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

    // 作排序
    list.stream()
            .sorted((p111, p222) -> p111.getPrice().compareTo(p222.getPrice()) )
            .forEach(System.out::println);

 

  stream 的終止操做(Terminal):一個流只能有一個 terminal 操做,當這個操做執行後,流就被用光了,沒法再被操做。因此這一定是流的最後一個操做。Terminal 操做的執行,纔會真正的開始流的遍歷,而且會生成一個結果。

    /**
     * 查找和匹配
     * allMatch 檢查是否匹配全部元素
     * anyMatch 檢查是否至少匹配一個元素
     * noneMatch 檢查是否沒有匹配全部的元素
     * findFirst 返回第一個元素
     * findAny 返回當前流中的任意元素
     * count 返回流中元素的總個數
     * max 返回流中最大值
     * min 返回流中最小值
     */
    boolean allMatchRes = list.stream().allMatch((e) -> e.getPrice()>30);
    System.out.println("allMatchRes = " + allMatchRes);

    boolean anyMatchRes = list.stream().anyMatch(e -> e.getPrice()>30);
    System.out.println("anyMatchRes = " + anyMatchRes);

    boolean noneMatchRes = list.stream().noneMatch(e ->e.getPrice() > 50);
    System.out.println("noneMatchRes = " + noneMatchRes);

    Optional<Product> findFirstRes = list.stream().findFirst();
    System.out.println("findFirstRes = " + findFirstRes.get());

 

   還有兩個強大的終止操做 reduce 和 collect 。

   reduce 操做是將流中元素反覆結合起來,獲得一個值

    /**
     * reduce : 規約操做
     */
    List<Integer> numList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    Integer count = numList.stream().reduce(0, (x, y) -> x+y);
    System.out.println("count = " + count);

    Optional<Integer> reduce = list.stream()
                    .map(Product::getPrice)
                    .reduce(Integer::sum); System.out.println(
"reduce = " + reduce);

 

   collect 操做:collect 操做將流轉換成其餘形式,接收一個 Collection 接口的實現,用於給 Stream 中元素作彙總的方法

    /**
     *  collect 收集操做
     */
    List priceList = list.stream()
                        .map(Product :: getPrice)
                        .collect(Collectors.toList());
    priceList.stream().forEach(System.out :: println);

 

  在對於一個 Stream 進行屢次轉換操做(Intermediate操做),每次都對 Stream 的每一個元素進行轉換,並且是執行屢次,這樣的時間複雜度就是 N 個 for 循環裏把全部操做都作完的總和嗎?其實不是這樣的,轉換操做都是 lazy 的,多個轉換操做只會在 Terminal 操做的時候融合起來,一次循環完成。咱們能夠這樣理解:Stream 裏有個操做函數的集合,每次轉換操做就是把轉換函數放入集合中,在 Terminal 操做的時候循環 Stream 對應的集合,而後對每一個元素執行全部的函數。so easy ! 

 

 

 並行流和串行流

   在 jdk1.8 新的 stream 包中,針對集合的操做提供了並行操做流和串行操做流。並行流就是把內容切割成多個數據塊,而且使用多個線程分別處理每一個數據塊的內容。Stream api 中聲明能夠經過 parallel() 與 sequential() 方法在並行流和串行流之間進行切換。

  jdk1.8 並行流使用的是 fork / join 框架進行並行操做。

 

ForkJoin 框架

  Fork / Join 框架:就是在必要的狀況下,將一個大的任務,進行拆分(fork)成若干小任務(拆分到不能拆分時),再將一個個的小任務運算結果進行 join 彙總。

  關鍵字:遞歸分合,分而治之

  減小了線程的等待時間,提升了性能。

 

 

Optional 容器

  使用 Optional 容器能夠快速的定位 NPE,而且在必定程度上能夠減小對參數非空檢驗的代碼量。

 

 

接口中能夠定義默認實現方法和靜態方法

  在接口中可使用 default 和 static 關鍵字來修飾接口中的普通方法。

  在 jdk1.8 中不少接口會新增方法,爲了保證 1.8 向下兼容, 1.7 版本中的接口實現類不用每一個都從新實現新添加的接口方法,引入了 default 默認實現,static 的用法是直接用接口調用方法便可。當一個類繼承父類又實現接口時,若後二者方法名相同,則優先繼承父類中的同名方法,即「類優先」,若是是實現兩個同名方法的接口,則要求實現類必須手動聲明默認實現哪一個接口中的方法。

 

 

新的日期 API     LocalDate | LocalTime | LocalDateTime

  新的日期 API 都是不可變的,更適用於多線程的使用環境中

    LocalDateTime dateTime = LocalDateTime.now();
    System.out.println("dateTime = " + dateTime);
    System.out.println(dateTime.getYear());
    System.out.println(dateTime.getMonthValue());
    System.out.println(dateTime.getDayOfMonth());
    System.out.println(dateTime.getHour());
    System.out.println(dateTime.getMinute());
    System.out.println(dateTime.getSecond());
    System.out.println(dateTime.getNano()); // 納秒

    LocalDateTime dateTime2 = LocalDateTime.of(2020, 3, 31, 20, 9);
    System.out.println("dateTime2 = " + dateTime2);

    LocalDateTime dateTime3 = dateTime2.plusHours(3);
    System.out.println("dateTime3 = " + dateTime3);

    Instant ins = Instant.now();
    System.out.println("ins = " + ins);

... 未完不續了 ...

  表示日期的 LocalDate

  表示時間的 LocalTime

  表示日期時間的 LocalDateTime

 

新的日期 API 的幾個優勢:

以前使用的 java.util.Date 月份從 0 開始,咱們通常會 加1 使用,很不方便,java.time.LocalDate 月份和星期都改爲 enum

java.util.Date 和 SimpleDateFormate 都不是線程安全的,而 LocalDate 和 LocalTime 和最基本的 String 同樣,是不變類型,不但線程安全,並且不能修改

java.util.Date 是一個「萬能接口」,它包含日期、時間、毫秒數,新接口更好用的緣由是考慮到了日期的操做,常常發生往前或日後推幾天的狀況,用 java.util.Date 配合Calendar 代碼較繁瑣。

 

 

 http://www.javashuo.com/article/p-pcszaryp-q.html

相關文章
相關標籤/搜索