J2SE一一JDK8新特性(吐血整理)

下面對幾個經常使用的特性作下重點說明。html

1、Lambda表達式

百科介紹java

函數編程很是關鍵的幾個特性以下:
(1)閉包與高階函數
函數編程支持函數做爲第一類對象,有時稱爲  閉包 或者  仿函數 (functor)對象。實質上,閉包是起函數的做用並能夠像對象同樣操做的對象。
與此相似,FP 語言支持  高階函數 。高階函數能夠用另外一個函數(間接地,用一個 表達式 ) 做爲其輸入參數,在某些狀況下,它甚至返回一個函數做爲其輸出參數。這兩種結構結合在一塊兒使得能夠用優雅的方式進行模塊化編程,這是使用 FP 的最大好處。
(2)惰性計算
在惰性計算中, 表達式 不是在綁定到變量時當即計算,而是在求值程序須要產生表達式的值時進行計算。延遲的計算使您能夠編寫可能潛在地生成無窮輸出的函數。由於不會計算多於程序的其他部分所須要的值,因此不須要擔憂由無窮計算所致使的 out-of-memory 錯誤。
(3)沒有「反作用」
所謂"反作用"(side effect),指的是函數內部與外部互動(最典型的狀況,就是修改全局變量的值),產生運算之外的其餘結果。函數式編程強調沒有"反作用",意味着函數要保持獨立,全部功能就是返回一個新的值,沒有其餘行爲,尤爲是不得修改外部變量的值。
綜上所述,函數式編程能夠簡言之是: 使用不可變值和函數, 函數對一個值進行處理, 映射成另外一個值。這個值在面嚮對象語言中能夠理解爲對象,另外這個值還能夠做爲函數的輸入。程序員

1.2 Lambda表達式

官方教程地址web

1.2.1 語法

完整的Lambda表達式由三部分組成:參數列表、箭頭、聲明語句;express

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {  statment1;  statment2;  //.............  return statmentM;}

1\. 絕大多數狀況,編譯器均可以從上下文環境中推斷出lambda表達式的參數類型,因此參數能夠省略:編程

(param1,param2, ..., paramN) -> {  statment1;  statment2;  //.............  return statmentM;}

二、 當lambda表達式的參數個數只有一個,能夠省略小括號:安全

param1 -> {  statment1;  statment2;  //.............  return statmentM;}

三、 當lambda表達式只包含一條語句時,能夠省略大括號、return和語句結尾的分號:數據結構

param1 -> statment

這個時候JVM會自動計算表達式值並返回,另外這種形式還有一種更簡寫法,方法引用寫法,具體能夠看下面的方法引用的部分。多線程

1.2.2 函數接口

函數接口是隻有一個抽象方法的接口, 用做 Lambda 表達式的返回類型。
接口包路徑爲java.lang.function,而後接口類上面都有@FunctionalInterface這個註解。下面列舉幾個較常見的接口類。閉包

image

這些函數接口在使用Lambda表達式時作爲返回類型,JDK定義了不少如今的函數接口,實際本身也能夠定義接口去作爲表達式的返回,只是大多數狀況下JDK定義的直接拿來就能夠用了。並且這些接口在JDK8集合類使用流操做時大量被使用。

1.2.3 類型檢查、類型推斷

Java編譯器根據 Lambda 表達式上下文信息就能推斷出參數的正確類型。 程序依然要通過類型檢查來保證運行的安全性, 但不用再顯式聲明類型罷了。 這就是所謂的類型推斷。Lambda 表達式中的類型推斷, 其實是 Java 7 就引入的目標類型推斷的擴展。

image

有時候顯式寫出類型更易讀,有時候去掉它們更易讀。沒有什麼法則說哪一種更好;對於如何讓代碼更易讀,程序員必須作出本身的選擇。

1.2.4 局部變量限制

Lambda表達式也容許使用自由變量(不是參數,而是在外層做用域中定義的變量),就像匿名類同樣。 它們被稱做捕獲Lambda。 Lambda能夠沒有限制地捕獲(也就是在其主體中引用)實例變量和靜態變量。但局部變量必須顯式聲明爲final,或事實上是final。
爲何局部變量有這些限制?
(1)實例變量和局部變量背後的實現有一個關鍵不一樣。實例變量都存儲在堆中,而局部變量則保存在棧上。若是Lambda能夠直接訪問局部變量,並且Lambda是在一個線程中使用的,則使用Lambda的線程,可能會在分配該變量的線程將這個變量收回以後,去訪問該變量。所以, Java在訪問自由局部變量時,其實是在訪問它的副本,而不是訪問原始變量。若是局部變量僅僅賦值一次那就沒有什麼區別了——所以就有了這個限制。
(2)這一限制不鼓勵你使用改變外部變量的典型命令式編程模式。

1.2.5 使用示例

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 20px; padding: 15px; font-weight: 400; position: relative; white-space: pre-wrap; overflow-wrap: normal; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; font-size: 13px; line-height: 1.42857; color: rgb(101, 123, 131); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; word-break: break-word; border: 1px solid rgb(204, 204, 204); background: rgb(246, 246, 246);">List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
long num = list.stream().filter( a -> a > 4 ).count();
System.out.println(num);</pre>

上面這段是統計list中大於4的值的個數,使用的lambda表達式爲 a-> a> 4 ,這裏參數a沒有定義類型,會自動判斷爲Integer類型,而這個表達式的值會自動轉化成函數接口Predicate對應的對象(filter方法定義的輸入參數類型),至於stream及相關的操做則是下面要說的流操做。它們常常一塊兒配合進行一塊兒數據處理。

2、流

2.1 流介紹

流是Java API的新成員,它容許你以聲明性方式處理數據集合(經過查詢語句來表達,而不是臨時編寫一個實現)。就如今來講,你能夠把它們當作遍歷數據集的高級迭代器。此外,流還能夠透明地並行處理,你無需寫任何多線程代碼了!

2.2 使用流

類別 方法名 方法簽名 做用
篩選切片 filter Stream<T> filter(Predicate<? super T> predicate) 過濾操做,根據Predicate判斷結果保留爲真的數據,返回結果仍然是流
  distinct Stream<T> distinct() 去重操做,篩選出不重複的結果,返回結果仍然是流
       
  limit Stream<T> limit(long maxSize) 截取限制操做,只取前 maxSize條數據,返回結果仍然是流
       
  skip Stream<T> skip(long n) 跳過操做,跳過n條數據,取後面的數據,返回結果仍然是流
       
映射 map <R> Stream<R> map(Function<? super T, ? extends R> mapper) 轉化操做,根據參數T,轉化成R類型,返回結果仍然是流
  flatMap <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 轉化操做,根據參數T,轉化成R類型流,這裏會生成多個R類型流,返回結果仍然是流
       
匹配 anyMatch boolean anyMatch(Predicate<? super T> predicate) 判斷是否有一條匹配,根據Predicate判斷結果中是否有一條匹配成功
  allMatch boolean allMatch(Predicate<? super T> predicate) 判斷是否全都匹配,根據Predicate判斷結果中是否所有匹配成功
       
  noneMatch boolean noneMatch(Predicate<? super T> predicate) 判斷是否一條都不匹配,根據Predicate判斷結果中是否全部的都不匹配
       
查找 findAny Optional<T> findAny() 查找操做, 查詢當前流中的任意元素並返回Optional
  findFirst Optional<T> findFirst() 查找操做, 查詢當前流中的第一個元素並返回Optional
       
歸約 reduce T reduce(T identity, BinaryOperator<T> accumulator); 歸約操做,一樣兩個類型的數據進行操做後返回相同類型的結果。好比兩個整數相加、相乘等。
  max Optional<T> max(Comparator<? super T> comparator) 求最大值,根據Comparator計算的比較結果獲得最大值
       
  min Optional<T> min(Comparator<? super T> comparator) 求最小值,根據Comparator計算的比較結果獲得最小值
       
彙總統計 collect <R, A> R collect(Collector<? super T, A, R> collector) 彙總操做,彙總對應的處理結果。這裏常常與
  count long count() 統計流中數據數量
       
遍歷 foreach void forEach(Consumer<? super T> action) 遍歷操做,遍歷執行Consumer 對應的操做

上面是Stream API的一些經常使用操做,按場景結合lambda表達式調用對應方法便可。至於Stream的生成方式,Stream的of方法或者Collection接口實現類的stream方法均可以得到對應的流對象,再進一步根據須要作對應處理。

另外上述方法若是返回是Stream對象時是能夠鏈式調用的,這個時候這個操做只是聲明或者配方,不產生新的集合,這種類型的方法是惰性求值方法;有些方法返回結果非Stream類型,則是及早求值方法。

「爲何要區分惰性求值和及早求值? 只有在對須要什麼樣的結果和操 做有了更多瞭解以後, 才能更有效率地進行計算。 例如, 若是要找出大於 10 的第一個數字, 那麼並不須要和全部元素去作比較, 只要找出第一個匹配的元素就夠了。 這也意味着能夠在集合類上級聯多種操做, 但迭代只需一次。這也是函數編程中惰性計算的特性,即只在須要產生表達式的值時進行計算。這樣代碼更加清晰,並且省掉了多餘的操做。

這裏還對上述列表操做中相關的Optional與Collectors類作下說明。

Optional類是爲了解決常常遇到的NullPointerException出現的,這個類是一個可能包含空值的容器類。用Optional替代null能夠顯示說明結果可能爲空或不爲空,再使用時使用isPresent方法判斷就能夠避免直接調用的空指針異常。

Collectors類是一個很是有用的是歸約操做工具類,工具類中的方法常與流的collect方法結合使用。好比
groupingBy方法能夠用來分組,在轉化Map時很是實用;partitioningBy方法能夠用來分區(分區能夠當作一種特殊的分組,真假值分組),joining方法能夠用來鏈接,這個應用在好比字符串拼接的場景。

2.3 並行流

Collection接口的實現類調用parallelStream方法就能夠實現並行流,相應地也得到了並行計算的能力。或者Stream接口的實現調用parallel方法也能夠獲得並行流。並行流實現機制是基於fork/join 框架,將問題分解再合併處理。

不過並行計算是否必定比串行快呢?這也不必定。實際影響性能的點包括:
(1)數據大小輸入數據的大小會影響並行化處理對性能的提高。 將問題分解以後並行化處理, 再將結果合併會帶來額外的開銷。 所以只有數據足夠大、 每一個數據處理管道花費的時間足夠多
時, 並行化處理纔有意義。
(2) 源數據結構
每一個管道的操做都基於一些初始數據源, 一般是集合。 將不一樣的數據源分割相對容易,這裏的開銷影響了在管道中並行處理數據時到底能帶來多少性能上的提高。
(3) 裝箱
處理基本類型比處理裝箱類型要快。
(4) 核的數量
極端狀況下, 只有一個核, 所以徹底不必並行化。 顯然, 擁有的核越多, 得到潛在性能提高的幅度就越大。 在實踐中, 核的數量不單指你的機器上有多少核, 更是指運行時你的機器能使用多少核。 這也就是說同時運行的其餘進程, 或者線程關聯性( 強制線程在某些核或 CPU 上運行) 會影響性能。
(5) 單元處理開銷
好比數據大小, 這是一場並行執行花費時間和分解合併操做開銷之間的戰爭。 花在流中
每一個元素身上的時間越長, 並行操做帶來的性能提高越明顯

實際在考慮是否使用並行時須要考慮上面的要素。在討論流中單獨操做每一塊的種類時, 能夠分紅兩種不一樣的操做: 無狀態的和有狀態的。無狀態操做整個過程當中沒必要維護狀態, 有狀態操做則有維護狀態所需的開銷和限制。若是能避開有狀態, 選用無狀態操做, 就能得到更好的並行性能。 無狀態操做包括 map、filter 和 flatMap, 有狀態操做包括 sorted、 distinct 和 limit。這種理解在理論上是更好的,固然實際使用仍是以測試結果最爲可靠 。

3、方法引用

方法引用的基本思想是,若是一個Lambda表明的只是「直接調用這個方法」,那最好仍是用名稱來調用它,而不是去描述如何調用它。事實上,方法引用就是讓你根據已有的方法實現來建立Lambda表達式。可是,顯式地指明方法的名稱,你的代碼的可讀性會更好。因此方法引用只是在內容中只有一個表達式的簡寫。

當 你 需 要使用 方 法 引用時 , 目 標引用 放 在 分隔符::前 ,方法 的 名 稱放在 後 面 ,即ClassName :: methodName 。例如 ,Apple::getWeight就是引用了Apple類中定義的方法getWeight。請記住,不須要括號,由於你沒有實際調用這個方法。方法引用就是Lambda表達式(Apple a) -> a.getWeight()的快捷寫法。

這裏有種狀況須要特殊說明,就是類的構造函數狀況,這個時候是經過ClassName::new這種形式建立Class構造函數對應的引用,例如:

image

4、默認方法

4.1 介紹

爲了以兼容方式改進API,Java 8中加入了默認方法。主要是爲了支持庫設計師,讓他們可以寫出更容易改進的接口。具體寫法是在接口中加default關鍵字修飾。

4.2 使用說明

默認方法因爲是爲了不兼容方式改進API才引入,因此通常正常開發中不會使用,除非你也想改進API,而不影響老的接口實現。固然在JDK8有大量的地方用到了默認方法,因此對這種寫法有必定的瞭解仍是有幫助的。
採用默認方法以後,你能夠爲這個方法提供一個默認的實現,這樣實體類就無需在本身的實現中顯式地提供一個空方法,而是默認就有了實現。

4.3 注意事項

因爲類能夠實現多個接口,也能夠繼承類,當接口或類中有相同函數簽名的方法時,這個時候到底使用哪一個類或接口的實現呢?
這裏有三個規則能夠進行判斷:
(1) 類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級。
(2) 若是沒法依據第一條進行判斷,那麼子接口的優先級更高:函數簽名相同時,優先選擇擁有最具體實現的默認方法的接口,即若是B繼承了A,那麼B就比A更加具體。
(3) 最後,若是仍是沒法判斷,繼承了多個接口的類必須經過顯式覆蓋和調用指望的方法,顯式地選擇使用哪個默認方法的實現。否則編譯都會報錯。

5、方法參數反射

官方教程地址

JDK8 新增了Method.getParameters方法,能夠獲取參數信息,包括參數名稱。不過爲了不.class文件由於保留參數名而致使.class文件過大或者佔用更多的內存,另外也避免有些參數( secret/password)泄露安全信息,JVM默認編譯出的class文件是不會保留參數名這個信息的。

這一選項需由編譯開關 javac -parameters 打開,默認是關閉的。在Eclipse(或者基於Eclipse的IDE)中能夠以下圖勾選保存:

image

6、日期/時間改進

1.8以前JDK自帶的日期處理類很是不方便,咱們處理的時候常常是使用的第三方工具包,好比commons-lang包等。不過1.8出現以後這個改觀了不少,好比日期時間的建立、比較、調整、格式化、時間間隔等。
這些類都在java.time包下。比原來實用了不少。

6.1 LocalDate/LocalTime/LocalDateTime

LocalDate爲日期處理類、LocalTime爲時間處理類、LocalDateTime爲日期時間處理類,方法都相似,具體能夠看API文檔或源碼,選取幾個表明性的方法作下介紹。

now相關的方法能夠獲取當前日期或時間,of方法能夠建立對應的日期或時間,parse方法能夠解析日期或時間,get方法能夠獲取日期或時間信息,with方法能夠設置日期或時間信息,plus或minus方法能夠增減日期或時間信息;

6.2 TemporalAdjusters

這個類在日期調整時很是有用,好比獲得當月的第一天、最後一天,當年的第一天、最後一天,下一週或前一週的某天等。

6.3 DateTimeFormatter

之前日期格式化通常用SimpleDateFormat類,可是不怎麼好用,如今1.8引入了DateTimeFormatter類,默認定義了不少常量格式(ISO打頭的),在使用的時候通常配合LocalDate/LocalTime/LocalDateTime使用,好比想把當前日期格式化成yyyy-MM-dd hh:mm:ss的形式:

LocalDateTime dt = LocalDateTime.now();DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");       System.out.println(dtf.format(dt));

7、參考資料

官方教程:http://docs.oracle.com/javase/tutorial/

《Java 8實戰》

《Java 8函數式編程》

相關文章
相關標籤/搜索