PDF文檔已上傳Github git
Github:https://github.com/zwjlpeng/Angrily_Learn_Java_8github
課本上說編程有兩種模式,面向過程的編程以及面向對象的編程,其實在面向對象編程以前還出現了面向函數的編程(函數式編程) ,之前一直被忽略、不被重視,如今從學術界已經走向了商業界,對函數編程語言的支持目前有Scala、Erlang、F#、Python、Php、Java、Javascript等,有人說他將會是編程語言中的下一個主流...express
爲何須要Lambda表達式?編程
1.使用Lambda表達式可使代碼變的更加緊湊,例如在Java中實現一個線程,只輸出一個字符串Hello World!,咱們的代碼以下所示:設計模式
public static void main(String[] args) throws Exception { new Thread(new Runnable() { @Override public void run() { System.out.println("Hello World!"); } }).start(); TimeUnit.SECONDS.sleep(1000); }
使用Lambda表達式以後代碼變成以下形式:數組
public static void main(String[] args) throws Exception { new Thread(() -> System.out.println("Hello World!")).start(); TimeUnit.SECONDS.sleep(1000); }
是否是代碼變的更緊湊了~,其餘的例如各類監聽器,以及事件處理器等均可以用這種方式進行簡化。多線程
2.修改方法的能力,其實說白了,就是函數中能夠接受以函數爲單元的參數,在C/C++中就是函數指針,在Java中就是Lambda表達式,例如在Java中使用集合類對一個字符串按字典序列進行排序,代碼以下所示:編程語言
public static void main(String[] args) { String []datas = new String[] {"peng","zhao","li"}; Arrays.sort(datas); Stream.of(datas).forEach(param -> System.out.println(param)); }
在上面代碼中用了Arrays裏的sort方法,如今咱們不須要按字典排序,而是按字符串的長度進行排序,代碼以下所示:ide
public static void main(String[] args) { String []datas = new String[] {"peng","zhao","li"}; Arrays.sort(datas,(v1 , v2) -> Integer.compare(v1.length(), v2.length())); Stream.of(datas).forEach(param -> System.out.println(param)); }
是否是很方便,咱們不須要實現Comparable接口,使用一個Lambda表達式就能夠改變一個函數的形爲~函數式編程
1.Lambda表達式的形式化表示以下所示
Parameters -> an expression
2.若是Lambda表達式中要執行多個語句塊,須要將多個語句塊以{}進行包裝,若是有返回值,須要顯示指定return語句,以下所示:
Parameters -> {expressions;};
3.若是Lambda表達式不須要參數,可使用一個空括號表示,以下示例所示
() -> {for (int i = 0; i < 1000; i++) doSomething();};
4.Java是一個強類型的語言,所以參數必需要有類型,若是編譯器可以推測出Lambda表達式的參數類型,則不須要咱們顯示的進行指定,以下所示,在Java中推測Lambda表達式的參數類型與推測泛型類型的方法基本相似,至於Java是如何處理泛型的,此處略去
String []datas = new String[] {"peng","zhao","li"}; Arrays.sort(datas,(String v1, String v2) -> Integer.compare(v1.length(), v2.length()));
上述代碼中 顯示指定了參數類型Stirng,其實不指定,以下代碼所示,也是能夠的,由於編譯器會根據Lambda表達式對應的函數式接口Comparator<String>進行自動推斷
String []datas = new String[] {"peng","zhao","li"};; Arrays.sort(datas,(v1, v2) -> Integer.compare(v1.length(), v2.length()));
5.若是Lambda表達式只有一個參數,而且參數的類型是能夠由編譯器推斷出來的,則能夠以下所示使用Lambda表達式,便可以省略參數的類型及括號
Stream.of(datas).forEach(param -> {System.out.println(param.length());});
6.Lambda表達式的返回類型,無需指定,編譯器會自行推斷,說是自行推斷
7.Lambda表達式的參數可使用修飾符及註解,如final、@NonNull等
函數式接口是Java 8爲支持Lambda表達式新發明的,在上面講述的Lambda Syntax時提到的sort排序方法就是一個樣例,在這個排序方法中就使用了一個函數式接口,函數的原型聲明以下所示
public static <T> void sort(T[] a, Comparator<? super T> c)
上面代碼中Comparator<? Super T>就是一個函數式接口,? Super T or ? entends T從Java 5支持泛型時開始引入,得理解清楚,在此忽略講述
什麼是函數式接口
1.函數式接口具備兩個主要特徵,是一個接口,這個接口具備惟一的一個抽像方法,咱們將知足這兩個特性的接口稱爲函數式接口,說到這,就不得不說一下接口中是有具體實現這個問題啦~
2.Lambda表達式不能脫離目標類型存在,這個目錄類型就是函數式接口,所下所示是一個樣例
String []datas = new String[] {"peng","zhao","li"}; Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length()); Arrays.sort(datas,comp); Stream.of(datas).forEach(param -> {System.out.println(param);});
Lambda表達式被賦值給了comp函數接口變量
3.函數式接口可使用@FunctionalInterface進行標註,使用這個標註後,主要有兩個優點,編譯器知道這是一個函數式接口,符合函數式的要求,另外一個就是生成Java Doc時會進行顯式標註
4.異常,若是Lambda表達式會拋出非運行時異常,則函數式接口也須要拋出異常,說白了,仍是一句話,函數式接口是Lambda表達式的目標類型
5.函數式接口中能夠定義public static方法,想一想在Java中咱們提供了Collection接口,同時還提供了一個Collections工具類等等,在Java中將這種Collections的實現轉移到了接口裏面,可是爲了保證向後兼容性,之前的這種Collection/Collections等邏輯均未改變
6.函數式接口能夠提供多個抽像方法,納尼!上面不是說只能有一個嘛?是的,在函數式接口中能夠提供多個抽像方法,但這些抽像方法限制了範圍,只能是Object類型裏的已有方法,爲何要這樣作呢?此處忽略,你們能夠自已研究
7.函數式接口裏面能夠定義方法的默認實現,以下所示是Predicate類的代碼,不只能夠提供一個default實現,並且能夠提供多個default實現呢,Java 8之前能夠嘛?我和個人小夥伴們都驚呆了,這也就致使出現了多繼承下的問題,想知道Java 8是如何對其進行處理的嘛,其實很Easy,後面我會再講~
8.爲何要提供default接口的實現?以下就是一個默認實現
default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); }
Java 8中在接口中增長了默認實現這種函數,其實在很大程序上違背了接口具備抽象這種特徵的,增長default實現主要緣由是由於考慮兼容及代碼的更改爲本,例如,在Java 8中向iterator這種接口增長一個方法,那麼實現這個接口的全部類都要需實現一遍這個方法,那麼Java 8須要更改的類就太多的,所以在Iterator接口裏增長一個default實現,那麼實現這個接口的全部類就都具備了這種實現,說白了,就是一個模板設計模式吧
有時,咱們須要執行的代碼在某些類中已經存在,這時咱們不必再去寫Lambda表達式,能夠直接使用該方法,這種狀況咱們稱之爲方法引用,以下所示,未採用方法引用前的代碼
以下所示
Stream.of(datas).forEach(param -> {System.out.println(param);});
使用方法引用後的代碼以下所示
Stream.of(datas).forEach(System.out::println);
以上示例使用的是out對象,下面示例使用的是類的靜態方法引用對字符串數組裏的元素忽略大小寫進行排序
String []datas = new String[] {"peng","Zhao","li"}; Arrays.sort(datas,String::compareToIgnoreCase); Stream.of(datas).forEach(System.out::println);
上面就是方法引用的一些典型示例
方法引用的具體分類
Object:instanceMethod
Class:staticMethod
Class:instanceMethod
上面分類中前兩種在Lambda表達式的意義上等同,都是將參數傳遞給方法,如上示例
System.out::println == x -> System.out.println(x)
最後一種分類,第一個參數是方法執行的目標,以下示例
String::compareToIgnoreCase == (x,y) -> x.compareToIgnoreCase(y)
還有相似於super::instanceMethod這種方法引用本質上與Object::instanceMethod相似
構造方法引用與方法引用相似,除了一點,就是構造方法引用的方法是new!如下是兩個示例
示例一:
String str = "test"; Stream.of(str).map(String::new).peek(System.out::println).findFirst();
示例二:
String []copyDatas = Stream.of(datas).toArray(String[]::new); Stream.of(copyDatas).forEach(x -> System.out.println(x));
總結一下,構造方法引用有兩種形式
Class::new Class[]::new
整體來講,Lambda表達式的變量做用域與內部類很是類似,只是條件相對來講,放寬了些之前內部類要想引用外部類的變量,必須像下面這樣
final String[] datas = new String[] { "peng", "Zhao", "li" }; new Thread(new Runnable() { @Override public void run() { System.out.println(datas); } }).start();
將變量聲明爲final類型的,如今在Java 8中能夠這樣寫代碼
String []datas = new String[] {"peng","Zhao","li"}; new Thread(new Runnable() { @Override public void run() { System.out.println(datas); } }).start();
也能夠這樣寫
new Thread(() -> System.out.println(datas)).start();
總之你愛怎麼寫,就怎麼寫吧,I don’t Care it!
看了上面的兩段代碼,可以發現一個顯著的不一樣,就是Java 8中內部類或者Lambda表達式對外部類變量的引用條件放鬆了,不要求強制的加上final關鍵字了,可是Java 8中要求這個變量是effectively final
What is effectively final?
Effectively final就是有效只讀變量,意思是這個變量能夠不加final關鍵字,可是這個變量必須是隻讀變量,即一旦定義後,在後面就不能再隨意修改,以下代碼會編譯出錯
String []datas = new String[] {"peng","Zhao","li"}; datas = null; new Thread(() -> System.out.println(datas)).start();
Java中內部類以及Lambda表達式中也不容許修改外部類中的變量,這是爲了不多線程狀況下的race condition
Lambda中變量以及this關鍵字
Lambda中定義的變量與外部類中的變量做用域相同,即外部類中定義了,Lambda就不能再重複定義了,同時在Lambda表達式使用的this關鍵字,指向的是外部類,你們能夠自行實踐下,此處略
未完待寫....