java 1.8

Java8新特性

1、重要數據結構和JVM的改動

1.HashMap的改動

HashMap維護了一個Entry數組,put(K key,V value)元素到HashMap中時經過key的hash碼計算其在數組中的索引位置,若索引位置上已有元素造成哈希碰撞. html


jdk1.8之前:哈希碰撞以後,在碰撞位置將會造成一個鏈表,新加入的元素將放置於表頭位置(明顯缺點:當碰撞元素過多,鏈表過長,遍歷鏈表查找元素的速度就較慢)java

jdk1.7關鍵代碼:
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }

    //若key爲null,則特殊處理一下(放到數組索引爲0的位置)
    if (key == null)
        return putForNullKey(value);

    //計算key在數組中對應的索引值
    int hash = hash(key);
    int i = indexFor(hash, table.length);

    //遍歷索引位置上的鏈表,若找到匹配key,則將新值賦給該節點,並返回舊值
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    //若沒有匹配的已存在的key,則在索引位置對應的鏈表上新增節點,next指向原鏈表的表頭元素
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

jdk1.8:(a)新加入的元素將置於鏈表的尾部;(b)當鏈表元素達到8個時,此鏈表將轉換爲紅黑樹 ,優勢:HashMap的查詢效率在jdk1.8中獲得了大大的提高sql

jdk1.8關鍵代碼:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //若map中的tab數組爲空,則先初始化數組
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //若tab數組中,當前元素hash值對應的索引位置上爲null,則直接將元素置於該位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //若元素與鏈表第一個元素的hash值和key值均相等,則直接將value賦給該元素
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //若當前位置是一個樹節點(說明已經轉爲紅黑樹了),則挨個與樹節點比較,若hash和key與某個節點相等則,則將value賦給該節點,不然新增一個樹節點
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //挨個遍歷鏈表中元素,若hash和key匹配,則找到匹配的元素
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    //若遍歷到表尾仍然沒有找到,則在尾部新增一個元素
                    p.next = newNode(hash, key, value, null);
                    //若元素個數達到8個,則將鏈表轉成紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key 
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

2.JVM運行時數據區

JVM運行時數據區去掉永久代(Permanent Generation ),改成元空間(MetaSpace)。數據庫

jdk1.8以前的JVM運行時數據區: 數組

方法區即永久代(Method Area): 全部線程共享,用於存儲JVM加載的類信息、常量、靜態變量、JIT編譯器編譯後的代碼等數據。它只是JVM規範中定義的一個概念,是堆的一個邏輯部分,爲了與普通堆區分開來,有一個別名叫作Non-Heap(非堆)。安全

jdk1.8及之後,永久代被移除,取而代之的是元空間,它直接從操做系統分配內存,獨立且能夠自由擴展,最大可分配空間就是系統可用空間,所以不會遇到PermGen的內存溢出錯誤(java.lang.OutOfMemoryError: PermGen space)。在JVM參數方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原來的-XX:PermSize和-XX:MaxPermSize。數據結構

2、關於接口的變更(例子:MyInterface.java)

一、default關鍵字

Jdk1.8新增了一個關鍵字default,用於修飾接口中的非抽象方法,只能經過接口實現類的對象進行調用app

二、static方法

Jdk1.8容許在接口中定義static方法,調用方式必須爲:接口名.方法名()eclipse

三、函數式接口

Jdk1.8提出了一個」函數式接口」的概念:只有一個抽象方法的接口稱爲函數式接口,可以使用@FunctionalInterface註解進行檢查(此註解只是用來檢查是不是函數式接口,並非說,加了此註解的接口是函數式接口,若是加了此註解,接口中有多個抽象方法將會編譯報錯)。常見的函數式接口:Runnable,Comparator等ide

3、Optional容器類(例子:MyTestOptional.java)

Optional 類(java.util.Optional) 是一個容器類,表明一個值存在或不存在,原來用 null 表示一個值不存在,如今 Optional 能夠更好的表達這個概念。而且能夠避免空指針異常。

經常使用方法:
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

4、Lambda表達式

Lambda表達式是對函數式接口中抽象方法的實現的簡寫方式.

1. 寫幾個例子感覺一下(例子: MyLambda.java)

2. 分析Lambda表達式的結構及常規寫法(例子: MyTestLambda.java)

格式:  ()->  {}
操做符「->」被稱爲 Lambda 操做符或箭頭操做符。它將 Lambda 分爲兩個部分:
左側: 指定了 Lambda 表達式須要的全部參數
右側: 指定了 Lambda 體,即 Lambda 表達式要執行的功能。

語法格式一:無參數,無返回值
    () -> System.out.println("Hello Lambda!");

語法格式二:有一個參數,而且無返回值
    (x) -> System.out.println(x)

語法格式三:有兩個以上的參數,有返回值,而且 Lambda 體中有多條語句
    Comparator<Integer> com = (x, y) -> {
        System.out.println("函數式接口");
        return Integer.compare(x, y);
    };

語法格式四: 
 (1).Lambda 表達式的參數列表的數據類型能夠省略不寫,jdk1.7引入的「類型推斷」機制在jdk1.8獲得了強化(注意:若不寫,都不寫)
 (2).若只有一個參數,小括號能夠省略不寫
 (3).若 Lambda 體中只有一條語句, return 和 大括號均可以省略不寫

3. jdk1.8內置的一些函數式接口

Lambda是基於函數式接口的,難道使用lambda表達式還必需要寫一個函數式接口?固然不是! jdk開發人員想到了這個問題.

Jdk提供了一些函數式接口:四大核心接口,N個擴展接口位於java.util.function包下.
四大核心函數式接口:
(1) 消費型接口Comsumer<T>:有一個參數無返回值的抽象方法,參數類型T
(2) 供給型接口Supplier<T>:無參數有返回值的抽象方法,返回值類型T
(3) 函數型接口Function<T,R>:一個參數有返回值的抽象方法,參數類型T,返回值類型R
(4) 斷言型接口Predicate<T>:一個參數有返回值的抽象方法,參數類型T,返回值類型boolean

5、方法引用和構造器引用

1.方法引用

當要傳遞給Lambda體的操做,已經有實現的方法了,可使用方法引用!(實現抽象方法的參數列表,必須與方法引用方法的參數列表保持一致!)

方法引用:使用操做符 「::」 將方法名和對象或類的名字分隔開來
例如:
Lambda表達式:  x->System.out.println(x);
    等同於:
方法引用(實例名::實例方法名):    System.out::println;

Lambda表達式:   t -> Integer.parseInt(t);
    等同於
方法引用(類名::類方法名):   Integer::parseInt;

Lambda表達式:   (x, y) -> x.equals(y);
    等同於
方法引用(類名::類方法名):   String::equals;

方法引用的判斷規則:
(1)方法引用所引用的方法的參數列表與返回值類型,須要與函數式接口中抽象方法的參數列表和返回值類型保持一致!
(2)若Lambda 的參數列表的第一個參數,是實例方法的調用者,第二個參數(或無參)是實例方法的參數時,格式: ClassName::MethodName

2.構造器引用(例子: MyTestMethodRef.java)

構造器的參數列表,須要與函數式接口中參數列表保持一致!
格式: 類名::new

6、Stream API

一、Stream簡介(幾個例子感覺一下Stream API的好處) (例子: MyTestStream1.java)

Stream 是 Java8 中處理集合的關鍵抽象概念,它能夠指定你但願對集合進行的操做,能夠執行很是複雜的查找、過濾和映射數據等操做。使用Stream API 對集合數據進行操做,就相似於使用 SQL 執行數據庫查詢。也可使用 Stream API 來並行執行操做。簡而言之,Stream API 提供了一種高效且易於使用的處理數據的方式。

注意:
(1)Stream 本身不會存儲元素。
(2)Stream 不會改變源對象。相反,他們會返回一個持有結果的新Stream。
(3)Stream 操做是延遲執行的。這意味着他們會等到須要結果的時候才執行。

二、建立stream: 經過一個數據源(如: 集合、數組), 獲取一個流(例子: MyTestStream2.java)

(1) Java8 中的 Collection 接口被擴展,提供了兩個獲取流的方法:
    default  Stream<E>  stream() : 返回一個順序流
    default  Stream<E>  parallelStream() : 返回一個並行流
(2) Java8 中的 Arrays 的靜態方法 stream() 能夠獲取數組流:
    public  static  <T>  Stream<T>  stream(T[] array): 返回一個流
    public  static  IntStream  stream(int[] array)
    public  static  LongStream  stream(long[] array)
    public  static  DoubleStream  stream(double[] array)
(3) 可使用靜態方法 Stream.of(), 經過顯示值建立一個流。它能夠接收任意數量的參數。
    public  static <T>  Stream<T>  of(T... values) : 返回一個流

三、中間操做: 一箇中間操做鏈,對數據源的數據進行處理(例子: MyTestStream3.java)

多箇中間操做能夠鏈接起來造成一個流水線,除非流水線上觸發終止操做,不然中間操做不會執行任何的處理!而在終止操做時一次性所有處理,稱爲「惰性求值」 。(例子: MyTestStream3.test1()).

(1) 篩選與切片
filter(Predicate  p): 過濾,從流中過濾出符合條件的元素,接收一個斷言型Lambda
distinct(): 去重複,根據流中元素的hashCode()和equals()方法去除流中的重複元素
limit(long  maxSize): 截斷流,使其元素不超過給定數量
skip(long  n): 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補

(2) 映射
map(Function f): 接收一個函數做爲參數,該函數會被應用到每一個元素上,並將其映射成一個新的元素。

(3) 排序
sorted(): 產生一個新流,其中按天然順序排序
sorted(Comparator  comp): 產生一個新流,其中按比較器順序排序

四、終止操做: 一個終止操做,執行中間操做鏈,併產生結果(例子: MyTestStream4.java)

(1)查找與匹配
allMatch(Predicate p): 檢查是否匹配全部元素
anyMatch(Predicate p): 檢查是否至少匹配一個元素
noneMatch(Predicate p): 檢查是否沒有匹配全部元素
findFirst(): 返回第一個元素
findAny(): 返回流中的任意元素
count(): 返回流中的元素總數
max(Comparator  c): 返回流中最大值
min(Comparator  c): 返回流中最小值
forEach(Consumer  c): 內部迭代
(2)規約
reduce(T  iden,BinaryOperator  b): 能夠將流中元素反覆結合起來,獲得一個值。返回 T
reduce(BinaryOperator  b): 能夠將流中元素反覆結合起來,獲得一個值。返回 Optional<T>
(3)收集
collect(Collector  c): 將流轉換爲其餘形式。接收一個 Collector接口的實現,用於給Stream中元素作彙總的方法, Collectors提供了豐富的靜態方法供咱們使用

五、幾個例子回顧一下stream的用法(例子: MyTestStream5.java)

7、新的時間日期API(java.time.*包)(例子: MyTestLocalDateTime.java)

Jdk1.8以前的時間日期API的缺陷:
1.Java的日期/時間類的定義不一致,在java.util和java.sql的包中都有日期類,此外用於格式化和解析的類在java.text包中定義。
2.java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期,將其歸入java.sql包並不合理。另外這兩個類都有相同的名字,這自己就是一個很是糟糕的設計。
3.全部的日期類都是可變的,所以他們都不是線程安全的,這是Java日期類最大的問題之一。
4.日期類並不提供國際化,沒有時區支持,所以Java引入了java.util.Calendar和java.util.TimeZone類,但他們一樣存在上述全部的問題。
5.計算兩個日期時間之間的間隔比較麻煩,所以出現了一個joda-time.jar.

Jdk1.8新的時間日期API解決了上述問題

1. 使用 LocalDate、 LocalTime、 LocalDateTime

LocalDate、 LocalTime、 LocalDateTime 類的實例是不可變的對象,分別表示使用ISO-8601日曆系統(ISO-8601日曆系統是國際標準化組織制定的現代公民的日期和時間的表示法)的日期、時間、日期和時間。

2. Duration 和 Period

(1) Duration:用於計算兩個「時間」間隔
(2) Period:用於計算兩個「日期」間隔

3. 解析與格式化

java.time.format.DateTimeFormatter 類

4. 按照指定時區獲取時間

PS:JDK1.8的新特性還有不少,這裏只是簡要介紹了一些咱們平時常常接觸到的.還有其餘的須要本身去深刻學習一下!

8、推薦一個代碼檢查插件:SonarLint

插件的介紹及用法: http://www.javashuo.com/article/p-gwrlneql-a.html 插件的安裝:將如下壓縮包中的plugins目錄中的jar包放到eclipse的plugins目錄,而後重啓便可

相關文章
相關標籤/搜索