JDK1.8的新特性 | 技術

2021-02-20 黑馬程式設計師

JDK1.8目前在企業中已經廣泛被應用,今天我們將學習以下方面的新特性:

· Lambda表達式

· 函數式接口

· 方法引用

· 接口的默認方法和靜態方法

· Optional

· Streams

· 並行數組

Lambda 表達式,也可稱為閉包,它是推動 Java 8 發布的最重要新特性。Lambda 允許把函數作為一個方法的參數(函數作為參數傳遞進方法中)。可以使代碼變的更加簡潔緊湊。

需要注意:

· 參數類型可省略,編譯器可以自己推斷

· 如果只有一個參數,圓括號可以省略

· 代碼塊如果只是一行代碼,大括號也可以省略

· 如果代碼塊是一行,且是有結果的表達式, return 可以省略

注意:事實上,把Lambda表達式可以看做是匿名內部類的一種簡寫方式。當然,前提是這個匿名內部類對應的必須

是接口,而且接口中必須只有一個函數!Lambda表達式就是直接編寫函數的:參數列表、代碼體、返回值等信息,

用函數來代替完整的匿名內部類 !

示例1:多個參數

準備一個集合:

假設我們要對集合排序,我們先看JDK7的寫法,需要通過匿名內部類來構造一個 Comparator :

如果是jdk8,我們可以使用新增的集合API:sort(Comparator c) 方法,接收一個比較器,我們用Lambda來代替Comparator 的匿名內部類:

對比一下 Comparator 中的 compare() 方法,你會發現:這裡編寫的Lambda表達式,恰恰就是 compare() 方法的簡寫形式,JDK8會把它編譯為匿名內部類。是不是簡單多了!

別著急,我們發現這裡的代碼塊只有一行代碼,符合前面的省略規則,我們可以簡寫為:

示例2:單個參數

還以剛才的集合為例,現在我們想要遍歷集合中的元素,並且列印。

先用jdk1.7的方式:

jdk1.8給集合添加了一個方法:foreach() ,接收一個對元素進行操作的函數:

實例3:把Lambda賦值給變量

Lambda表達式的實質其實還是匿名內部類,所以我們其實可以把Lambda表達式賦值給某個變量。

不過上面的用法很少見,一般都是直接把Lambda作為參數。

示例4:隱式final

Lambda表達式的實質其實還是匿名內部類,而匿名內部類在訪問外部局部變量時,要求變量必須聲明為 final !不過我們在使用Lambda表達式時無需聲明 final ,這並不是說違反了匿名內部類的規則,因為Lambda底層會隱式的把變量設置為 final ,在後續的操作中,一定不能修改該變量:

正確示範:

錯誤案例:

經過前面的學習,相信大家對於Lambda表達式已經有了初步的了解。總結一下:

· Lambda表達式是接口的匿名內部類的簡寫形式

· 接口必須滿足:內部只有一個函數

其實這樣的接口,我們稱為函數式接口,我們學過的 Runnable 、Comparator 都是函數式接口的典型代表。但是在實踐中,函數接口是非常脆弱的,只要有人在接口裡添加多一個方法,那麼這個接口就不是函數接口了,就會導致編譯失敗。Java 8提供了一個特殊的註解@FunctionalInterface 來克服上面提到的脆弱性並且顯示地表明函數接口。而且jdk8版本中,對很多已經存在的接口都添加了@FunctionalInterface 註解,例如 Runnable 接口:

另外,Jdk8默認提供了一些函數式接口供我們使用:

Function代表的是有參數,有返回值的函數。還有很多類似的Function接口:

看出規律了嗎?這些都是一類函數接口,在Function基礎上衍生出的,要麼明確了參數不確定返回結果,要麼明確結果不知道參數類型,要麼兩者都知道。

Consumer系列與Function系列一樣,有各種衍生接口,這裡不一一列出了。不過都具備類似的特徵:那就是不返回任何結果。

Supplier系列,英文翻譯就是「供應者」,顧名思義:只產出,不收取。所以不接受任何參數,返回T類型結果。

方法引用使得開發者可以將已經存在的方法作為變量來傳遞使用。方法引用可以和Lambda表達式配合使用。

可以看到這個方法接收兩個參數:

· List<T> list :需要進行轉換的集合

· Function<T,R> :函數接口,接收T類型,返回R類型。用這個函數接口對list中的元素T進行轉換,變為R類型

接下來,我們看具體案例:

① 類的靜態方法引用

我們需要把這個集合中的元素轉為十六進位保存,需要調用Integer.toHexString() 方法:

這個方法接收一個 i 類型,返回一個 String 類型,可以用來構造一個 Function 的函數接口:

我們先按照Lambda原始寫法,傳入的Lambda表達式會被編譯為 Function 接口,接口中通過Integer.toHexString(i) 對原來集合的元素進行轉換:

上面的Lambda表達式代碼塊中,只有對 Integer.toHexString() 方法的引用,沒有其它代碼,因此我們可以直接把方法作為參數傳遞,由編譯器幫我們處理,這就是靜態方法引用:

② 類的非靜態方法引用

接下來,我們把剛剛生成的 String 集合 hexList 中的元素都變成大寫,需要藉助於String類的toUpperCase()方法:

這次是非靜態方法,不能用類名調用,需要用實例對象,因此與剛剛的實現有一些差別,我們接收集合中的每一個字符串 s 。但與上面不同然後 s 不是 toUpperCase() 的參數,而是調用者:

因為代碼體只有對 toUpperCase() 的調用,所以可以把方法作為參數引用傳遞,依然可以簡寫:

下面一個需求是這樣的,我們先定義一個數字 Integer num = 2000 ,然後用這個數字和集合中的每個數字進行比較,比較的結果放入一個新的集合。比較對象,我們可以用 Integer 的 compareTo 方法:

與前面類似,這裡Lambda的代碼塊中,依然只有對 num.compareTo(i) 的調用,所以可以簡寫。但是,需要注意的是,這次方法的調用者不是集合的元素,而是一個外部的局部變量 num ,因此不能使用Integer::compareTo ,因為這樣是無法確定方法的調用者。要指定調用者,需要用  對象::方法名 的方式:

最後一個場景:把集合中的數字作為毫秒值,構建出 Date 對象並放入集合,這裡我們就需要用到Date的構造函數:

我們可以接收集合中的每個元素,然後把元素作為 Date 的構造函數參數:

上面的Lambda表達式實現方式,代碼體只有 new Date() 一行代碼,因此也可以採用方法引用進行簡寫。但問題是,構造函數沒有名稱,我們只能用 new 關鍵字來代替:

注意兩點:

· 上面代碼中的System.out::println 其實是 指定對象System.out的非靜態方法println的引用

· 如果構造函數有多個,可能無法區分導致傳遞失敗

Java 8使用兩個新概念擴展了接口的含義:默認方法和靜態方法。

默認方法使得開發者可以在 不破壞二進位兼容性的前提下,往現存接口中添加新的方法,即不強制那些實現了該接口的類也同時實現這個新加的方法。

默認方法和抽象方法之間的區別在於抽象方法需要實現,而默認方法不需要。接口提供的默認方法會被接口的實現類繼承或者覆寫,例子代碼如下:

Defaulable接口使用關鍵字default定義了一個默認方法notRequired()。DefaultableImpl類實現了這個接口,同時默認繼承了這個接口中的默認方法;OverridableImpl類也實現了這個接口,但覆寫了該接口的默認方法,並提供了一個不同的實現。

Java 8帶來的另一個有趣的特性是在接口中可以定義靜態方法,我們可以直接用接口調用這些靜態方法。例子代碼如下:

下面的代碼片段整合了默認方法和靜態方法的使用場景:

這段代碼的輸出結果如下:

由於JVM上的默認方法的實現在字節碼層面提供了支持,因此效率非常高。默認方法允許在不打破現有繼承體系的基礎上改進接口。該特性在官方庫中的應用是:給 java.util.Collection 接口添加新方法,如 stream() 、

parallelStream() 、 forEach() 和 removeIf() 等等。

儘管默認方法有這麼多好處,但在實際開發中應該謹慎使用:在複雜的繼承體系中,默認方法可能引起歧義和編譯錯誤。如果你想了解更多細節,可以參考官方文檔。

Java應用中最常見的bug就是空值異常。

Optional 僅僅是一個容器,可以存放T類型的值或者 null 。它提供了一些有用的接口來避免顯式的 null 檢查,可以參考Java 8官方文檔了解更多細節。

接下來看一點使用Optional的例子:可能為空的值或者某個類型的值:

如果 Optional 實例持有一個非空值,則 isPresent() 方法返回 true ,否則返回 false ;如果 Optional 實例持有null , orElseGet() 方法可以接受一個lambda表達式生成的默認值;map() 方法可以將現有的 Optional 實例的值轉換成新的值;orElse() 方法與 orElseGet() 方法類似,但是在持有null的時候返回傳入的默認值,而不是通過Lambda來生成。

上述代碼的輸出結果如下:

這個例子的輸出是:

新增的Stream API(java.util.stream)將生成環境的函數式編程引入了Java庫中。這是目前為止最大的一次對Java庫的完善,以便開發者能夠寫出更加有效、更加簡潔和緊湊的代碼。

Steam API極大得簡化了集合操作(後面我們會看到不止是集合),首先看下這個叫Task的類:

Task類有一個points屬性,另外還有兩種狀態:OPEN或者CLOSED。現在假設有一個task集合:

首先看一個問題:在這個task集合中一共有多少個OPEN狀態的?計算出它們的points屬性和。在Java 8之前,要解決這個問題,則需要使用foreach循環遍歷task集合;但是在Java 8中可以利用steams解決:包括一系列元素的列表,並且支持順序和並行處理。

運行這個方法的控制臺輸出是:

這裡有很多知識點值得說。首先, tasks 集合被轉換成 steam 表示;其次,在 steam 上的 filter 操作會過濾掉所有CLOSED 的 task ;第三, mapToInt 操作基於 tasks 集合中的每個 task 實例的 Task::getPoints 方法將 task流轉換成 Integer 集合;最後,通過 sum 方法計算總和,得出最後的結果。

在學習下一個例子之前,還需要記住一些steams(點此更多細節)的知識點。Steam之上的操作可分為中間操作和晚期操作。

中間操作會返回一個新的steam——執行一個中間操作(例如filter)並不會執行實際的過濾操作,而是創建一個新的steam,並將原steam中符合條件的元素放入新創建的steam。

晚期操作(例如forEach或者sum),會遍歷steam並得出結果或者附帶結果;在執行晚期操作之後,steam處理線已經處理完畢,就不能使用了。在幾乎所有情況下,晚期操作都是立刻對steam進行遍歷。

steam的另一個價值是創造性地支持並行處理(parallel processing)。對於上述的tasks集合,我們可以用下面的代碼計算所有task的points之和:

這裡我們使用parallel方法並行處理所有的task,並使用reduce方法計算最終的結果。控制臺輸出如下:

對於一個集合,經常需要根據某些條件對其中的元素分組。利用steam提供的API可以很快完成這類任務,代碼如下:

控制臺的輸出如下:

最後一個關於tasks集合的例子問題是:如何計算集合中每個任務的點數在集合中所佔的比重,具體處理的代碼如下:

控制臺輸出結果如下:

最後,正如之前所說,Steam API不僅可以作用於Java集合,傳統的IO操作(從文件或者網絡一行一行得讀取數據)可以受益於steam處理,這裡有一個小例子:

Stream的方法 onClose() 返回一個等價的有額外句柄的Stream,當Stream的 close() 方法被調用的時候這個句柄會被執行。Stream API、Lambda表達式還有接口默認方法和靜態方法支持的方法引用,是Java 8對軟體開發的現代範式的響應。

Java8版本新增了很多新的方法,用於支持並行數組處理。最重要的方法是 parallelSort() ,可以顯著加快多核機器上的數組排序。下面的例子論證了parallexXxx系列的方法:

上述這些代碼使用parallelSetAll()方法生成20000個隨機數,然後使用parallelSort()方法進行排序。這個程序會輸出亂序數組和排序數組的前10個元素。上述例子的代碼輸出的結果是:

相關焦點

  • win10安裝jdk1.8以及配置環境變量和多個jdk之間的切換
    ~本次的安裝是在新電腦上安裝的jdk1.7 /1.8/11三個版本.均為學習使用.多個版本可能會帶來未知問題.折騰無止境,不用懼怕.先上jdk1.8的安裝過程.jdk1.8下載.2. jdk安裝:安裝過程和普通軟體沒有區別,一路next.(需要注意的一點:不要修改路徑,就讓安裝在系統盤C盤.)3.
  • 詳解java並發原子類AtomicInteger(基於jdk1.8源碼分析)
    一、從a++說起為什麼使用AtomicInteger我們知道java並發機制中主要有三個特性需要我們去考慮,原子性、可見性和有序性。synchronized關鍵字可以保證可見性和有序性卻無法保證原子性。而這個AtomicInteger的作用就是為了保證原子性。我們先看一個例子。在上面的這個例子中,我們定義了一個變量a。並且使用了5個線程分別去增加。
  • OpenJdk1.8筆記——java啟動流程
    Jdk中java的入口函數文件為openjdk\jdk\src\share\bin\main.c中的main方法(window上為WinMain),然後調用jdk8u-dev/jdk/src/share/bin/java.c的JLI_Launch方法,啟動一個jvm虛擬機;程序入口
  • JDK/Java 16 可能帶來什麼新特性?
    按 InfoWorld  所述,截至 2020 年 11 月 30 日,已有 14 個特性已被正式提了出來,還有 2 個特性則是預測性質。 孵化器階段的矢量 API,其中 JDK 將配備一個孵化器模塊,jdk.incubator.vector,以表達可在支持的 CPU 架構上編譯為最佳矢量硬體指令的矢量計算,以實現優於等效標量計算的性能。 將 JDK 移植到 Windows / AArch64平臺。
  • JDK/Java 14 發布 - OSCHINA - 中文開源技術交流社區
    總共 16 個新特性如下:305:Pattern Matching for instanceof (Preview)為 instanceof 運算符引入模式匹配(預覽階段)通過模式匹配,開發者可以用更簡潔和更安全的方式來表達通用的程序邏輯。
  • 剛剛,JDK 12 早期試用版發布了!你學的過來嗎?
    參考http://jdk.java.net/12https://download.java.net/java/early_access/jdk12/docs/apihttp://hg.openjdk.java.net/jdk/jdk/log?
  • JDK/Java 15 發布 - OSCHINA - 中文開源技術交流社區
    該特性主要用在特定領域的類,這些類主要用於保存數據,不提供領域行為。目前處於第 2 個預覽版本階段。379: Shenandoah: A Low-Pause-Time Garbage Collector (Production)Shenandoah 垃圾回收從實驗特性變為產品特性。這是一個從 JDK 12 引入的回收算法,該算法通過與正在運行的 Java 線程同時進行疏散工作來減少 GC 暫停時間。
  • Java 11新特性:棄用Nashorn JavaScript引擎
    Java 11將聲明棄用Nashorn JavaScript腳本引擎,相關API和jjs工具,並在未來的版本中移除這些特性。Nashorn JavaScript引擎在Java 8中首次引入,完整實現了ECMAScript-262 5.1標準。
  • 融合了 JavaScript 之力的 Nashorn 或被 JDK 11 棄用
    從 JDK 6 開始,Java 就捆綁了基於 Mozilla 的 Rhino 的 JavaScript 引擎,該特性允許開發者將 JavaScript 代碼嵌入到 Java 中,甚至從嵌入的 JavaScript 中調用
  • shell-安裝jdk腳本
    前言在Linux安裝jdk是很簡單的事情,那就讓shell腳本去做吧!安裝到oracle官網的歸檔網址下載需要的jdk壓縮包,並放到腳本所在的目錄,然後cd到腳本目錄執行就可以,這裡是1.8.172版本為例:http://www.oracle.com/technetwork/java/archive-139210.html#!
  • Java 15 即將到來,新特性速覽!
    在發布前夕,我們不妨先一窺新版 JDK 的新特性:第二個外部內存訪問 API(孵化階段),它將使 Java 程序安全高效地訪問 Java 堆以外的外部存儲器。該 API 應該能夠在各種外部內存(如本機、持久和託管堆)上進行操作。
  • 【終極版】Java8 新特性全面介紹,強烈建議收藏
    Nashorn, JavaScript 引擎:Java 8提供了一個新的Nashorn javascript引擎,它允許我們在JVM上運行特定的javascript應用有很多人認為,Java 8 的一些新特性另 Java 開發人員十分滿意,在本篇文章中,我們將詳細介紹 Java 8 的這些新特性!
  • Java核心技術精講PDF掃描版下載
    《Java核心技術精講》內容包括Java簡介、Java基礎語法、面向對象、異常的捕獲及處理、包及訪問控制權限、Java新特性、多線程、常用類庫、JavaIO操作、網絡編程、類集框架、Java資料庫編程、DAO設計模式、Eclipse開發工具。
  • 新平臺新技能 玩家必須了解的技術特性
    新平臺新技能 玩家必須了解的技術特性此外,新架構CPU內存繼續支持DDR3L-1600、LPDDR3-1866、DDR4-2133,而Core i3同樣閹割掉了AMT、TSX-NI技術。Speed Shift技術   在新的技術特性方面,Kaby Lake獲得了Speed Shift技術的升級,可以增強Turbo Boost睿頻的靈活性,在短時睿頻段加強處理器的穩定性
  • jdk環境變量配置教程
    下面,小編就來跟大家講解jdk環境變量配置的操作技巧。jdk環境變量配置教程1、右擊「我的電腦」,點擊屬性2、點擊:高級系統設置。3、在彈出的系統屬性中,選擇高級,在點擊環境變量。4、新建系統變量JAVA_HOME,變量值為jdk安裝目錄5、找到Path變量,然後點擊編輯。
  • Java15正式發布, 14個新特性,刷新你的認知!!
    不過沒關係,多了解一下,多掌握一點新東西,對你來說沒有壞處。新特性JDK 15 新特性一覽表: ID JEP Feature
  • Windows下jdk下載安裝與環境變量配置
    下載安裝jdk百度搜索jdk+版本,以1.8版本為例,百度搜索「jdk1.8」,一般是第一個。百度搜索jdk1.8打開jdk下載頁面,這裡我們下載jdk1.8 x64版本下載地址:https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html
  • 如何在Windows10系統中配置java的JDK環境
    操作步驟如下:1.下載好 jdk 的安裝文件,我下載的是 jdk-10.0.1_windows-x64_bin.exe 這個版本的安裝文件;2.使用滑鼠雙擊該exe文件,該exe文件會運行安裝界面,截圖如下:3.安裝程序自動執行,界面如下:
  • getty 1.3 版本發布,兼容 JDK1.7、Android 5.0 以上版本
    部分網友表示希望兼容更低jdk版本,特別是Android 5.0以上版本的支持(Getty 1.3以下版本使用了部分jdk8的特徵,在安卓8.0以下不支持)。本次更新主要是對在JDK1.8以下環境以及Android8.0 以下環境下使用做了兼容。 優化了部分代碼,提高了穩定性。