Java 中的 Lambda 表達(dá)式通常使用 (argument) -> (body) 語法書寫,例如:
(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }
以下是一些 Lambda 表達(dá)式的例子:
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };
Lambda 表達(dá)式的結(jié)構(gòu)
讓我們了解一下 Lambda 表達(dá)式的結(jié)構(gòu)法梯。
- 一個(gè) Lambda 表達(dá)式可以有零個(gè)或多個(gè)參數(shù)
- 參數(shù)的類型既可以明確聲明昧穿,也可以根據(jù)上下文來推斷镣屹。例如:(int a)與(a)效果相同
- 所有參數(shù)需包含在圓括號(hào)內(nèi)尽棕,參數(shù)之間用逗號(hào)相隔戚哎。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
- 空?qǐng)A括號(hào)代表參數(shù)集為空项栏。例如:() -> 42
- 當(dāng)只有一個(gè)參數(shù)浦辨,且其類型可推導(dǎo)時(shí),圓括號(hào)()可省略沼沈。例如:a -> return a*a
- Lambda 表達(dá)式的主體可包含零條或多條語句
- 如果 Lambda 表達(dá)式的主體只有一條語句流酬,花括號(hào){}可省略。匿名函數(shù)的返回類型與該主體表達(dá)式一致
- 如果 Lambda 表達(dá)式的主體包含一條以上語句列另,則表達(dá)式必須包含在花括號(hào){}中(形成代碼塊)芽腾。匿名函數(shù)的返回類型與代碼塊的返回類型一致,若沒有返回則為空
什么是函數(shù)式接口
在 Java 中页衙,Marker(標(biāo)記)類型的接口是一種沒有方法或?qū)傩月暶鞯慕涌谔希?jiǎn)單地說,marker 接口是空接口店乐。相似地艰躺,函數(shù)式接口是只包含一個(gè)抽象方法聲明的接口。
java.lang.Runnable 就是一種函數(shù)式接口眨八,在 Runnable 接口中只聲明了一個(gè)方法 void run()腺兴,相似地,ActionListener 接口也是一種函數(shù)式接口廉侧,我們使用匿名內(nèi)部類來實(shí)例化函數(shù)式接口的對(duì)象页响,有了 Lambda 表達(dá)式篓足,這一方式可以得到簡(jiǎn)化。
每個(gè) Lambda 表達(dá)式都能隱式地賦值給函數(shù)式接口拘泞,例如纷纫,我們可以通過 Lambda 表達(dá)式創(chuàng)建 Runnable 接口的引用。
Runnable r = () -> System.out.println("hello world");
當(dāng)不指明函數(shù)式接口時(shí)陪腌,編譯器會(huì)自動(dòng)解釋這種轉(zhuǎn)化:
new Thread(
() -> System.out.println("hello world")
).start();
因此辱魁,在上面的代碼中,編譯器會(huì)自動(dòng)推斷:根據(jù)線程類的構(gòu)造函數(shù)簽名 public Thread(Runnable r) { }诗鸭,將該 Lambda 表達(dá)式賦給 Runnable 接口染簇。
以下是一些 Lambda 表達(dá)式及其函數(shù)式接口:
Consumer<Integer> c = (int x) -> { System.out.println(x) };
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
Predicate<String> p = (String s) -> { s == null };
是 Java 8 新加入的一種接口,用于指明該接口類型聲明是根據(jù) Java 語言規(guī)范定義的函數(shù)式接口强岸。Java 8 還聲明了一些 Lambda 表達(dá)式可以使用的函數(shù)式接口锻弓,當(dāng)你注釋的接口不是有效的函數(shù)式接口時(shí),可以使用 @FunctionalInterface 解決編譯層面的錯(cuò)誤蝌箍。
以下是一種自定義的函數(shù)式接口: @FunctionalInterface public interface WorkerInterface {
public void doSomeWork();
}
根據(jù)定義青灼,函數(shù)式接口只能有一個(gè)抽象方法,如果你嘗試添加第二個(gè)抽象方法妓盲,將拋出編譯時(shí)錯(cuò)誤杂拨。例如:
@FunctionalInterface
public interface WorkerInterface {
public void doSomeWork();
public void doSomeMoreWork();
}
錯(cuò)誤:
Unexpected @FunctionalInterface annotation
@FunctionalInterface ^ WorkerInterface is not a functional interface multiple
non-overriding abstract methods found in interface WorkerInterface 1 error
Lambda 表達(dá)式舉例
線程可以通過以下方法初始化:
//舊方法:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from thread");
}
}).start();
//新方法:
new Thread(
() -> System.out.println("Hello from thread")
).start();
事件處理可以使用 Java 8 的 Lambda 表達(dá)式解決。下面的代碼中悯衬,我們將使用新舊兩種方式向一個(gè) UI 組件添加 ActionListener:
//Old way:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("The button was clicked using old fashion code!");
}
});
//New way:
button.addActionListener( (e) -> {
System.out.println("The button was clicked. From Lambda expressions !");
});
以下代碼的作用是打印出給定數(shù)組中的所有元素弹沽。注意,使用 Lambda 表達(dá)式的方法不止一種筋粗。在下面的例子中策橘,我們先是用常用的箭頭語法創(chuàng)建 Lambda 表達(dá)式,之后娜亿,使用 Java 8 全新的雙冒號(hào)(::)操作符將一個(gè)常規(guī)方法轉(zhuǎn)化為 Lambda 表達(dá)式:
//Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}
//New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
//or we can use :: double colon operator in Java 8
list.forEach(System.out::println);
在下面的例子中丽已,我們使用斷言(Predicate)函數(shù)式接口創(chuàng)建一個(gè)測(cè)試,并打印所有通過測(cè)試的元素买决,這樣促脉,你就可以使用 Lambda 表達(dá)式規(guī)定一些邏輯,并以此為基礎(chǔ)有所作為:
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Main {
public static void main(String [] a) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
System.out.println("Print all numbers:");
evaluate(list, (n)->true);
System.out.println("Print no numbers:");
evaluate(list, (n)->false);
System.out.println("Print even numbers:");
evaluate(list, (n)-> n%2 == 0 );
System.out.println("Print odd numbers:");
evaluate(list, (n)-> n%2 == 1 );
System.out.println("Print numbers greater than 5:");
evaluate(list, (n)-> n > 5 );
}
public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
for(Integer n: list) {
if(predicate.test(n)) {
System.out.println(n + " ");
}
}
}
}
下面的例子使用 Lambda 表達(dá)式打印數(shù)值中每個(gè)元素的平方策州,注意我們使用了 .stream() 方法將常規(guī)數(shù)組轉(zhuǎn)化為流。Java 8 增加了一些超棒的流 APIs宫仗。
接口包含許多有用的方法够挂,能結(jié)合 Lambda 表達(dá)式產(chǎn)生神奇的效果。我們將 Lambda 表達(dá)式
x -> x*x
傳給 map() 方法藕夫,該方法會(huì)作用于流中的所有元素孽糖。之后枯冈,我們使用 forEach 方法打印數(shù)據(jù)中的所有元素:
//Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
int x = n * n;
System.out.println(x);
}
//New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
下面的例子會(huì)計(jì)算給定數(shù)值中每個(gè)元素平方后的總和。請(qǐng)注意办悟,Lambda 表達(dá)式只用一條語句就能達(dá)到此功能尘奏,這也是 MapReduce 的一個(gè)初級(jí)例子。我們使用 map() 給每個(gè)元素求平方病蛉,再使用 reduce() 將所有元素計(jì)入一個(gè)數(shù)值:
//Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
int x = n * n;
sum = sum + x;
}
System.out.println(sum);
//New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);