咱們知道接口是沒法直接被實例化的,你只能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表達式這一章在: docs.oracle.com/javase/tuto… )。 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。數據庫
直接翻譯的話就是: 可能被調用的一個函數或者是子程序,可是沒有綁定到一個標識符上。express
咱們放到java裏來理解一下,衆所周知java是面向對象的,函數也是放在一個類中的。調用方法的話就大體有如下兩種形式:編程
因此在java中,咱們姑且就能夠將類名+方法名稱之爲一個標識符,由於咱們經過類名(嚴謹的說是全類名)+方法名定位到一個方法。()-> System.out.println("hello world"),就是一個匿名類和匿名方法的組合化簡以後產物,類名沒有直接給出,方法名也是沒有,這些統統是編譯器來給。bash
百度百科是這樣描述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。主流的編程語言應該都有這個概念。
咱們來想一想爲何能夠化簡到這種地步,假如咱們有這樣兩個方法:
咱們稱之爲方法重載,同一個方法名,參數類型不一樣。咱們在調用的時候,編譯器或者說是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表達式的形式,在根據形式去講解如何使用。
方法參數的話 new Thread(()-> System.out.println("hello world")).start(); 這個就是一個例子。雖然原則上講並不該該直接建立一個線程,應該經過線程池。 咱們知道函數的參數類型能夠是基本類型和引用類型,new Thread(()-> System.out.println("hello world")) 這個例子好像就是將一個方法做爲參數傳遞進去了同樣。
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);
複製代碼
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