Lambda 表達式是在Java 8中引入的,并且成為了Java 8亮點。它使得功能性程式設計變得非常便利,極大地簡化了開發工作。
讓我們從最簡單的例子開始,來學習如何對一個string清單進行排序。我們首先使用Java 8之前的方法來實作:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
靜态工具方法Collections.sort接受一個list,和一個Comparator接口作為輸入參數,Comparator的實作類可以對輸入的list中的元素進行比較。通常情況下,你可以直接用建立匿名Comparator對象,并把它作為參數傳遞給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編譯器能夠自動識别參數的類型,是以你就可以省略掉類型不寫。可以看出:相對于之前使用匿名内部類的方式,Java8的lambda表達式更精簡。
Lambda表達式用處
1、凡是有匿名内部類的地方,都可以用Lambda表達式簡化。
2、Java8 Stream集合之流式操作,方法參數均為Lambda表達式。
Lambda文法解析
我們在此抽象一下lambda表達式的一般文法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
Lambda表達式的定義
Lambda表達式是:一段帶有輸入參數的可執行語句塊,它不用被綁定到一個辨別符上,即不需要指派給一個變量,并且它将來可能被調用。
上面的例子
(String a, String b) -> {return b.compareTo(a)}
其實就是一個lambda表達式,并且還可以簡寫,省略參數類型和return,是以就成為最後的精簡版:
(a, b) -> b.compareTo(a)
一個Lambda表達式具有下面這樣的文法特征。它由三個部分組成:
- 第一部分為一個括号(),裡面用逗号分隔的參數清單,參數即函數式接口裡面方法的參數;
- 第二部分為一個箭頭符号:->;
- 第三部分為一個大括号{},裡面是多條語句構成的方法體,可以是表達式和代碼塊。
簡寫版本說明
- 參數類型省略,編譯器都可以從上下文環境中推斷出lambda表達式的參數類型。
- 當lambda表達式的參數個數隻有一個,可以省略小括号。
- 當lambda表達式隻包含一條語句時,可以省略大括号、return和語句結尾的分号。
- 如果沒有參數則隻需(),例如 Thread 中的 run 方法就沒有參數傳入,當它使用 Lambda 表達式後:
Thread t = new Thread(() -> { System.out.println("Hello from a thread in run");
下面列舉了Lambda表達式的幾個最重要的特征:
● 可選的類型聲明:你不用去聲明參數的類型。編譯器可以從參數的值來推斷它是什麼類型。
● 可選的參數周圍的括号:你可以不用在括号内聲明單個參數。但是對于很多參數的情況,括号是必需的。
● 可選的大括号:如果表達式體裡面隻有一個語句,那麼你不必用大括号括起來。
● 可選的傳回關鍵字:如果表達式體隻有單個表達式用于值的傳回,那麼編譯器會自動完成這一步。若要訓示表達式來傳回某個值,則需要使用大括号。
函數式接口
Lambda表達式如何比對Java的類型系統?語言的設計者們思考了很多如何讓現有的功能和lambda表達式友好相容。于是就有了函數式接口這個概念。函數式接口是一種隻有一個方法的接口,函數式接口可以隐式地轉換成 Lambda 表達式。
每一個lambda都能夠通過一個特定的接口,與一個給定的類型進行比對。一個所謂的函數式接口必須要有且僅有一個抽象方法聲明。每個與之對應的lambda表達式必須要與抽象方法的聲明相比對。
函數式接口的重要屬性是:我們能夠使用 Lambda 執行個體化它們,Lambda 表達式讓你能夠将函數作為方法參數,或者将代碼作為資料對待。Lambda 表達式的引入給開發者帶來了不少優點:在 Java 8 之前,匿名内部類,監聽器和事件處理器的使用都顯得很冗長,代碼可讀性很差,Lambda 表達式的應用則使代碼變得更加緊湊,可讀性增強。
要使用 Lambda 表達式,需要定義一個函數式接口,這樣往往會讓程式充斥着過量的僅為 Lambda 表達式服務的函數式接口。為了減少這樣過量的函數式接口,Java 8 在 java.util.function 中增加了不少新的函數式通用接口,即内置函數式接口。
内置函數式接口
Predicates預言式接口
Predicate<T> :将 T 作為輸入,傳回一個布爾值作為輸出,該接口包含多種預設方法來将 Predicate 組合成其他複雜的邏輯(與、或、非)。
Predicate是一個布爾類型的函數接口,該函數隻有一個輸入參數。Predicate接口包含了多種預設方法,用于處理複雜的邏輯動詞(and, or,negate)
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
Functions功能式接口
Function<T, R>:将 T 作為輸入,傳回 R 作為輸出,他還包含了和其他函數組合的預設方法。
Function接口接收一個參數,并傳回單一的結果。預設方法可以将多個函數串在一起(compse, andThen)。
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
Consumers
Consumer<T> :将 T 作為輸入,不傳回任何内容,表示在單個參數上的操作。
Consumer代表了在一個輸入參數上需要進行的操作。
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Suppliers
Supplier接口産生一個給定類型的結果。與Function不同的是,Supplier沒有輸入參數。
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
Comparators
Comparator接口在早期的Java版本中非常著名。Java 8 為這個接口添加了不同的預設方法。
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
Lambda表達式通路其外部變量
以前java的匿名内部類在通路外部變量的時候,外部變量必須用final修飾。在java8對這個限制做了優化,可以不用顯示使用final修飾,但是編譯器隐式當成final來處理。
可以得到以下結論:
● 可通路 static 修飾的成員變量,如果是 final static 修飾,不可再次指派,隻有 static 修飾可再次指派;
● 可通路表達式外層的 final 局部變量(不用聲明為 final,隐性具有 final 語義),不可再次指派。
Java 8接口的增強
Java 8 對接口做了進一步的增強。在接口中可以添加使用 default 關鍵字修飾的非抽象方法。還可以在接口中定義靜态方法。如今,接口看上去與抽象類的功能越來越類似了。
預設方法
Java 8 還允許我們給接口添加一個非抽象的方法實作,隻需要使用 default 關鍵字即可,這個特征又叫做擴充方法。在實作該接口時,該預設擴充方法在子類上可以直接使用,它的使用方式類似于抽象類中非抽象成員方法。但擴充方法不能夠重載 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重載。
靜态方法
在接口中,還允許定義靜态的方法。接口中的靜态方法可以直接用接口來調用。