咱們知道接口是沒法直接被實例化的,你只能new他的實現類,可是假如當前接口只有一個抽象方法,又不想再建立一個類,在JDK8以前,你就只能使用匿名內部類。像下面這樣:html
new Thread(new Runnable() { @Override public void run() { System.out.println("hello world"); } }).start();
如今的IDE已經很強大了,你只用寫到new Runnable()就會幫你把代碼補全,可是你仍是要移動光標到run方法體內,寫對應的代碼。在學到匿名內部類的時候,我就但願代碼變的簡潔緊湊一些 (事實上官方寫的指導書《The Java™ Tutorials》也是將Lambda式放在匿名類這一章下面的,介紹Lambda表達式這一章在: https://docs.oracle.com/javas... )。
JDK8以後上面的代碼就能夠化簡成下面這樣:java
new Thread(()-> System.out.println("hello world")).start();
怎麼樣是否是變得至關簡潔了呢! ()-> System.out.println("hello world") 就能夠稱之爲Lambda表達式。Lambda是λ這個符號的名稱。
維基百科是這樣描述Lambda表達式的:sql
a function (or a subroutine) defined, and possibly called, without being bound to an identifier。
直接翻譯的話就是: 可能被調用的一個函數或者是子程序,可是沒有綁定到一個標識符上。數據庫
咱們放到java裏來理解一下,衆所周知java是面向對象的,函數也是放在一個類中的。調用方法的話就大體有如下兩種形式:express
因此在java中,咱們姑且就能夠將類名+方法名稱之爲一個標識符,由於咱們經過類名(嚴謹的說是全類名)+方法名定位到一個方法。()-> System.out.println("hello world"),就是一個匿名類和匿名方法的組合化簡以後產物,類名沒有直接給出,方法名也是沒有,這些統統是編譯器來給。編程
百度百科是這樣描述Lambda表達式的:oracle
Lambda 表達式(lambda expression)是一個匿名函數,Lambda表達式基於數學中的λ演算得名,直接對應於其中的lambda抽象(lambda abstraction),是一個匿名函數,即沒有函數名的函數。--《百度百科》
咱們知道數學中的演算是須要運算符(演算符的),調用一個函數是須要給定參數的,是須要方法體的。app
() 裏面就能夠放參數,-> 是運算符,-> 以後就是方法體。形如()->{}的表達式,在java中咱們就能夠稱之爲Lambda表達式。
由於Runnable接口中的run方法沒有參數,因此()裏面啥也沒寫。
順便提一下,Lambda表達式並不算是java率先提出。2007年11月19,.NET Framework 3.5就已經引入了,jdk8的發佈時間是2014-03-18。主流的編程語言應該都有這個概念。編程語言
咱們來想一想爲何能夠化簡到這種地步,假如咱們有這樣兩個方法:ide
咱們稱之爲方法重載,同一個方法名,參數類型不一樣。咱們在調用的時候,編譯器或者說是JVM,就能根據咱們給定的參數來調用對應的方法。那麼new Thread(()-> System.out.println("hello world"))來推斷這是Thread的哪個構造函數也是可以作到的事情。因此看似你寫的是這個:
new Thread(()-> System.out.println("hello world")).start();
事實上編譯器會幫你補成:
new Thread(new Runnable() { @Override public void run() { System.out.println("hello world"); } }).start();
這也是一些人說在某些狀況下Lambda性能不佳的緣由。
若是你打開Runnable接口看的話,會發現Runnable接口上有一個註解: @FunctionalInterface。FunctionalInterface意味函數式接口,什麼是函數式接口呢? 就是一個接口中只有一個抽象方法的接口,咱們稱之爲函數式接口。咱們知道,jdk8以前接口中只能放抽象方法,jdk8以後能夠放非抽象方法。方法中要加上default關鍵字,咱們稱之爲默認實現。形式以下:
@FunctionalInterface public interface MyLambdaInterface { void sayHello(String str); default void sayWorld(String str){ System.out.println(str); } }
加上@FunctionalInterface的接口,只能有一個抽象方法,容許存在和Object中方法名相同的方法,但要求參數類型一致。不然會通不過編譯。以下所示:
@FunctionalInterface public interface MyLambdaInterface { void sayHello(String str); int hashCode(); boolean equals(Object obj); default void sayWorld(String str){ System.out.println(str); } }
Lambda表達式的適用場景即爲: 假如我有一個函數式接口,可是呢,我又不想建立這個類的顯示實現類(顯示實現類即爲新建Class文件),我又想使用這個方法,Lambda表達式大顯身手的就到了。
咱們首先介紹Lambda表達式的形式,在根據形式去講解如何使用。
Lambda從形式上來看更像是一個函數,(函數所須要的參數)->{方法實現}
方法參數的話 new Thread(()-> System.out.println("hello world")).start(); 這個就是一個例子。雖然原則上講並不該該直接建立一個線程,應該經過線程池。
咱們知道函數的參數類型能夠是基本類型和引用類型,new Thread(()-> System.out.println("hello world")) 這個例子好像就是將一個方法做爲參數傳遞進去了同樣。
以MyLambdaInterface爲例,方法引用咱們能夠這麼寫:
MyLambdaInterface myLambdaInterface = (str)-> System.out.println(str);
從形式上來說,myLambdaInterface好像指向一個函數同樣。
(str)-> System.out.println(str);就是方法sayHello的實現。
指向已存在的方法的語法爲:
public class Person { LocalDate birthday; public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); } }
方法引用的寫法: Person::compareByAge
可是這句話直接出如今代碼中會報錯,咱們須要找一個函數式接口來指向它。函數式接口中的方法返回類型和接收參數要和compareByAge保持一致,像下面這樣。
public interface CompareTo<T> { int compare(T t1,T t2); }
CompareTo<Person> p = Person::compareByAge;
String[] stringArray = {"Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda"}; Arrays.sort(stringArray, String::compareToIgnoreCase);
形式: 類名:: new。用法和前面三種是一致的,能夠當作一個函數式接口的實現,也能夠當作一個方法參數。
Lambda表達式本質上也是內部類,那麼同內部類同樣,要想要訪問局部變量,那麼這個局部變量必定要是final類型的。
如今咱們來總結一下Lambda表達式的寫法:
形式大體是這樣: (函數式接口所需的參數)->方法體;
假如方法體只有一行,能夠不用寫{
例如:
new Thread(()-> System.out.println()).start();
假若有多行的話須要寫{
咱們能夠認爲每個Lambda表達式都是一個函數式接口的匿名實現。
JDK也內置了一些函數式接口,這些函數式接口大多都在java.util.function下,理解這些函數式接口是學習Stream API的關鍵。這裏咱們選取幾個比較經典的講解一下。
boolean test(T t); 這個方法接收一個參數,返回一個boolean類型的值。
咱們能夠這樣寫:
Predicate<String> predicate = (str)-> str.contains("s");
還能夠這樣寫:
List<String> list = new ArrayList<>(); Predicate<String> predicate = list::add; predicate.test("add");//等價於調用list.add("add") predicate.test("bb");
Consumer、 Function、 Supplier用法相似,這裏再也不作過多的介紹。
首先Stream是一個接口,位於java.util.stream下。 假如咱們的集合泛型是一個學生類,像下面這樣。
List<Student> student = new ArrayList<>();
那麼假如我像統計年齡在18歲以上的學生總數,那麼在不用流的狀況下,咱們一般是這樣作的。
int count = 0; List<Student> studentList = new ArrayList<>(); for (Student student : studentList) { if (student.getAge() > 18){ count++; } }
Student能夠映射進數據庫中,咱們在數據庫作這樣的事情是簡單的,那麼在代碼可否像sql同樣執行count,where操做呢。
有了流以後就能夠,咱們能夠這樣寫:
studentStream.filter( (student) -> student.getAge() > 18).count();
是否是變的簡單多了呢! 那我能不能group by 在count呢,也是能夠的。按性別分組求出男生和女生的人數。
Map<Boolean, Long> result = studentStream.collect(Collectors.groupingBy(Student::isSex, counting()));
細究他的實現的話,這個並非本篇的主題,本篇只是簡單入門,詳細討論是另外一篇了。
順便提一下, 流有兩種類型,串行和並行流,這將是咱們下一篇文章重點闡釋的內容。
參考資料: The Java™ Tutorials