2019年9月19日java13已正式發佈,感嘆java社區強大,經久不衰。因爲國內偏保守,新東西總要放一放,讓其餘人踩踩坑,等穩定了纔會去用。而且企業目的仍是賺錢,更不會由於一個新特性去重構代碼,再開發一套程序出來。甚者國內大多傳統企業還在用java4 、五、6…java
今天講一講 java8
的新特性,Java 8 (又稱爲 jdk 1.8) 是 Java 語言開發的一個主要版本。Oracle 公司於 2014 年 3 月 18 日發佈 Java 8 ,它支持函數式編程,新的日期 API,新的Stream API 等。是Java5以後一個大的版本升級,讓Java語言和庫彷彿得到了新生,核心新特性包含:程序員
Java8 函數式接口− 函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,可是能夠有多個非抽象方法的接口。函數式接口能夠被隱式轉換爲 lambda 表達式。數據庫
Lambda 表達式 − Lambda 容許把函數做爲一個方法的參數(函數做爲參數傳遞到方法中)。express
默認方法 − 默認方法就是一個在接口裏面有了一個實現的方法。編程
方法引用 − 方法引用提供了很是有用的語法,能夠直接引用已有Java類或對象(實例)的方法或構造器。與lambda聯合使用,方法引用可使語言的構造更緊湊簡潔,減小冗餘代碼。api
Stream API −新添加的Stream API(java.util.stream) 把真正的函數式編程風格引入到Java中。數組
Date Time API − 增強對日期與時間的處理。安全
Optional 類 − Optional 類已經成爲 Java 8 類庫的一部分,用來解決空指針異常。markdown
函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,可是能夠有多個非抽象方法的接口。
函數式接口能夠被隱式轉換爲 lambda 表達式。框架
JDK 1.8 以前已有的函數式接口:
java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.nio.file.PathMatcher java.lang.reflect.InvocationHandler java.beans.PropertyChangeListener java.awt.event.ActionListener javax.swing.event.ChangeListener
JDK 1.8 新增長的函數接口:
java.util.function
這裏說一下@FunctionalInterface
註解,這個註解是java8新出得一個註解。
咱們經常使用的一些接口Callable、Runnable、Comparator等在JDK8中都添加了@FunctionalInterface註解。
追蹤源碼查看@FunctionalInterface註解javadoc
經過JDK8源碼javadoc,能夠知道這個註解有如下特色:
該註解只能標記在」有且僅有一個抽象方法」的接口上。
JDK8接口中的靜態方法和默認方法,都不算是抽象方法。
接口默認繼承Java.lang.Object,因此若是接口顯示聲明覆蓋了Object中方法,那麼也不算抽象方法。
該註解不是必須的,若是一個接口符合」函數式接口」定義,那麼加不加該註解都沒有影響。加上該註解可以更好地讓編譯器進行檢查。若是編寫的不是函數式接口,可是加上了@FunctionInterface,那麼編譯器會報錯。
@FunctionalInterface標記在接口上,「函數式接口」是指僅僅只包含一個抽象方法的接口。
若是一個接口中包含不止一個抽象方法,那麼不能使用@FunctionalInterface,編譯會報錯。
好比下面這個接口就是一個正確的函數式接口:
簡單說,默認方法就是接口能夠有實現方法,並且不須要實現類去實現其方法。
咱們只需在方法名前面加個 default 關鍵字便可實現默認方法。
爲何要有這個特性?
首先,以前的接口是個雙刃劍,好處是面向抽象而不是面向具體編程,缺陷是,當須要修改接口時候,須要修改所有實現該接口的類,目前的 java 8 以前的集合框架沒有 foreach 方法,一般能想到的解決辦法是在JDK裏給相關的接口添加新的方法及實現。然而,對於已經發布的版本,是無法在給接口添加新方法的同時不影響已有的實現。因此引進的默認方法。他們的目的是爲了解決接口的修改與現有的實現不兼容的問題。
這裏值得注意的是,咱們知道java中一個類能夠實現多個接口 若是多個接口中有同名同參同返回值得默認方法須要咱們在實現類重寫該方法,不然會編譯報錯。
這是java8的一大重要特性,咱們知道java是面嚮對象語言,把行爲封裝成一個對象是咱們根深蒂固的java編程思想,可是lambda正好反其道而行之,是一種面向過程的編程思想。
不在贅述更多模糊,概念的含義,你們如今有這樣的一個認識就能夠了。下面我會用例子帶入你們。
可以接收Lambda表達式的參數類型,是一個有且僅有一個抽象方法,可是能夠有多個非抽象方法的接口。「函數接口」。能夠頂替匿名內部類。
語法:
(parameters) -> expression或(parameters) ->{ statements; }
如下是lambda表達式的重要特徵:
可選類型聲明:不須要聲明參數類型,編譯器能夠統一識別參數值。
可選的參數圓括號:一個參數無需定義圓括號,但多個參數須要定義圓括號。
可選的大括號:若是主體包含了一個語句,就不須要使用大括號。
可選的返回關鍵字:若是主體只有一個表達式返回值則編譯器會自動返回值,大括號須要指定明表達式返回了一個數值。
老版本Java中排列字符串
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } });
只須要給靜態方法 Collections.sort 傳入一個List對象以及一個比較器來按指定順序排列。一般作法都是建立一個匿名的比較器對象而後將其傳遞給sort方法。
在Java 8 中你就不必使用這種傳統的匿名對象的方式了,直接上lambda
:
Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });
看到了吧,代碼變得更段且更具備可讀性,可是實際上還能夠寫得更短:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
對於函數體只有一行代碼的,你能夠去掉大括號{}以及return關鍵字,可是你還能夠寫得更短點:
Collections.sort(names, (a, b) -> b.compareTo(a));
據官方文檔中介紹Java編譯器能夠自動推導出參數類型,能夠不寫,可是這裏我仍是建議你們寫出參數類型,方便代碼被其餘人閱讀時候的可讀性。本身反過來查看代碼也有幫助。
缺點 : 學習成本稍高,剛開始接觸不容易理解,並須要反覆練習。
優勢 : lambda表達式讓咱們能夠把一個方法當成參數傳遞進另外一個方法,頂替匿名內部類消除了樣板式代碼。並讓咱們的代碼看起來更加簡潔、乾淨。
而且lambda表達式能夠在結合不少地方使用。下面涉及我會再分析。總的來講lambda表達式仍是值得咱們學習的。
Java 8 API添加了一個新的抽象稱爲流Stream,可讓你以一種聲明的方式處理數據。
Stream 使用一種相似用 SQL 語句從數據庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。
Stream API能夠極大提升Java程序員的生產力,讓程序員寫出高效率、乾淨、簡潔的代碼。
這種風格將要處理的元素集合看做一種流, 流在管道中傳輸, 而且能夠在管道的節點上進行處理, 好比篩選, 排序,聚合等。
元素流在管道中通過中間操做(intermediate operation)的處理,最後由最終操做(terminal operation)獲得前面處理的結果。
Stream(流)是一個來自數據源的元素隊列並支持聚合操做。
元素是特定類型的對象,造成一個隊列。Java中的Stream並不會存儲元素,而是按需計算。
數據源 流的來源。能夠是集合,數組,I/O channel, 產生器generator 等。
聚合操做 相似SQL語句同樣的操做, 好比filter, map, reduce, find, match, sorted等。
和之前的Collection操做不一樣, Stream操做還有兩個基礎的特徵:
Pipelining: 中間操做都會返回流對象自己。這樣多個操做能夠串聯成一個管道, 如同流式風格(fluent style)。這樣作能夠對操做進行優化, 好比延遲執行(laziness)和短路( short-circuiting)。
內部迭代:之前對集合遍歷都是經過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫作外部迭代。Stream提供了內部迭代的方式, 經過訪問者模式(Visitor)實現。
在 Java 8 中, 集合接口有兩個方法來生成流:
stream() − 爲集合建立串行流。
parallelStream() − 爲集合建立並行流。
parallelStream 是流並行處理程序的代替方法。如下實例咱們使用 parallelStream 來輸出空字符串的數量:
Collectors 結合 collect()方法後使用 Collectors.joining(String 分隔符) Collectors.toList()變爲集合
Collectors 類實現了不少歸約操做,例如將流轉換成集合和聚合元素。Collectors 可用於返回列表或字符串:
方法引用經過方法的名字來指向一個方法。
方法引用可使語言的構造更緊湊簡潔,減小冗餘代碼。
方法引用使用一對冒號 :: 。
下面,咱們在 Car 類中定義了 4 個方法做爲例子來區分 Java 中 4 種不一樣方法的引用。
構造器引用:它的語法是Class::new,或者更通常的Class< T >::new實例以下:
finalCarcar=Car.create(Car::new); finalList<Car>cars=Arrays.asList(car);
靜態方法引用:它的語法是Class::static_method,實例以下:
cars.forEach(Car::collide);
特定類的任意對象的方法引用:它的語法是Class::method實例以下:
cars.forEach(Car::repair);
特定對象的方法引用:它的語法是instance::method實例以下:
finalCarpolice = Car.create(Car::new); cars.forEach(police::follow);
方法引用實例
在 Java8Tester.java 文件輸入如下代碼:
實例中咱們將 System.out::println 方法做爲靜態方法來引用。
執行以上腳本,輸出結果爲:
Java 8經過發佈新的Date-Time API (JSR 310)來進一步增強對日期與時間的處理。
由於Java的Date
,Calendar
類型使用起來並非很方便,並且Date類(聽說)有着線程不安全等諸多弊端。同時若不進行封裝,會在每次使用時特別麻煩。因而Java8推出了線程安全、簡易、高可靠的時間包。而且數據庫中也支持LocalDateTime類型,在數據存儲時候使時間變得簡單。Java8此次新推出的包括三個相關的時間類型:LocalDateTime年月日十分秒;LocalDate日期;LocalTime時間;三個包的方法都差很少。
列出經常使用api,詳細的使用網上大片,你們自行查找:
//獲取當前時間的LocalDateTime對象 //LocalDateTime.now(); //根據年月日構建LocalDateTime //LocalDateTime.of(); //比較日期前後 //LocalDateTime.now().isBefore(), //LocalDateTime.now().isAfter(),
Optional不是對null關鍵字的一種替代,而是對於null斷定提供了一種更加優雅的實現。
NullPointException能夠說是全部java程序員都遇到過的一個異常,雖然java從設計之初就力圖讓程序員脫離指針的苦海,可是指針確實是實際存在的,而java設計者也只能是讓指針在java語言中變得更加簡單、易用,而不能徹底的將其剔除,因此纔有了咱們平常所見到的關鍵字null。
空指針異常是一個運行時異常,對於這一類異常,若是沒有明確的處理策略,那麼最佳實踐在於讓程序早點掛掉,可是不少場景下,不是開發人員沒有具體的處理策略,而是根本沒有意識到空指針異常的存在。
當異常真的發生的時候,處理策略也很簡單,在存在異常的地方添加一個if語句斷定便可,可是這樣的應對策略會讓咱們的程序出現愈來愈多的null斷定,咱們知道一個良好的程序設計,應該讓代碼中儘可能少出現null關鍵字,而java8所提供的Optional
類則在減小NullPointException的同時,也提高了代碼的美觀度。但首先咱們須要明確的是,它並 不是對null關鍵字的一種替代,而是對於null斷定提供了一種更加優雅的實現,從而避免NullPointException。
假設咱們須要返回一個字符串的長度,若是不借助第三方工具類,咱們須要調用str.length()方法:
if(null == str) { // 空指針斷定 return 0; } return str.length();
若是採用Optional類,實現以下:
return Optional.ofNullable(str).map(String::length).orElse(0)
Optional的代碼相對更加簡潔,當代碼量較大時,咱們很容易忘記進行null斷定,可是使用Optional類則會避免這類問題。
Optional<String> optStr = Optional.empty();
上面的示例代碼調用empty()方法建立了一個空的Optional對象型。
建立對象:不容許爲空
Optional提供了方法of()用於建立非空對象,該方法要求傳入的參數不能爲空,不然拋NullPointException,示例以下:
// 當str爲null的時候,將拋出NullPointException Optional<String> optStr = Optional.of(str);
// 若是str是null,則建立一個空對象 Optional<String> optStr = Optional.ofNullable(str);
這裏Optional也提供了兩個基本的流失處理:映射和過濾。
爲了演示,咱們設計了一個User類,以下:
public class User { /** 用戶編號 */ private long id; private String name; private int age; private Optional<Long> phone; private Optional<String> email; public User(String name, int age) { this.name = name; this.age = age; } // 省略setter和getter }
手機和郵箱不是一我的的必須有的,因此咱們利用Optional定義。
映射是將輸入轉換成另一種形式的輸出的操做
好比前面例子中,咱們輸入字符串,而輸出的是字符串的長度,這就是一種隱射,咱們利用方法map()得以實現。假設咱們但願得到一我的的姓名,那麼咱們能夠以下實現:
String name = Optional.ofNullable(user).map(User::getName).orElse("no name");
這樣當入參user不爲空的時候則返回其name,不然返回no name
若是咱們但願經過上面方式獲得phone或email,利用上面的方式則行不通了,由於map以後返回的是Optional,咱們把這種稱爲Optional嵌套,咱們必須在map一次才能拿到咱們想要的結果:
long phone = optUser.map(User::getPhone).map(Optional::get).orElse(-1L);
其實這個時候,更好的方式是利用flatMap,一步拿到咱們想要的結果:
long phone = optUser.flatMap(User::getPhone).orElse(-1L);
iliter,顧名思義是過濾的操做,咱們能夠將過濾操做作爲參數傳遞給該方法,從而實現過濾目的
假如咱們但願篩選18週歲以上的成年人,則能夠實現以下:
optUser.filter(u -> u.getAge() >= 18).ifPresent(u -> System.out.println("Adult:" + u));
默認行爲是當Optional爲不知足條件時所執行的操做,好比在上面的例子中咱們使用的orElse()就是一個默認操做,用於在Optional對象爲空時執行特定操做,固然也有一些默認操做是當知足條件的對象存在時執行的操做。
get()
get用於獲取變量的值,可是當變量不存在時則會拋出NoSuchElementException,因此若是不肯定變量是否存在,則不建議使用
orElse(Tother)
當Optional的變量不知足給定條件時,則執行orElse,好比前面當str爲null時,返回0。
orElseGet(Supplier<? extends X> expectionSupplier)
若是條件不成立時,須要執行相對複雜的邏輯,而不是簡單的返回操做,則可使用orElseGet實現:
long phone = optUser.map(User::getPhone).map(Optional::get).orElseGet(() -> { // do something here return -1L; });
與get()方法相似,都是在不知足條件時返回異常,不過這裏咱們能夠指定返回的異常類型。
ifPresent(Consumer<? super T>)
當知足條件時執行傳入的參數化操做。
Optional是一個final類,未實現任何接口,因此當咱們在利用該類包裝定義類的屬性的時候,若是咱們定義的類有序列化的需求,那麼由於Optional沒有實現Serializable接口,這個時候執行序列化操做就會有問題:
public class User implements Serializable{ /** 用戶編號 */ private long id; private String name; private int age; private Optional<Long> phone; // 不能序列化 private Optional<String> email; // 不能序列化 不過咱們能夠採用以下替換策略: private long phone; public Optional<Long> getPhone() { return Optional.ofNullable(this.phone); }
今天分享到此結束,經過本篇文章瞭解java8的新特性不只強大並且感受不少地方都能立刻使用起來,好比經過stream流處理集合數據結合lambda寫出高效、乾淨、簡潔的代碼,經過Optional類優雅的處理NPE。rd們裝X的最高境界就是寫一手其餘rd們看不懂又以爲很高大上的代碼了吧。哈哈哈。。。本着學習的態度,若是文章內有出入地方請指出,看到留言後我會和你們討論學習。