什么是lambda表達(dá)式
看看lambda表達(dá)式在其他語(yǔ)言中怎么使用的:
在python中有l(wèi)ambda表達(dá)式带污,在python中可以使用它定義匿名方法哼凯,并賦值給一個(gè)變量锉屈,然后那個(gè)變量就成為了一個(gè)方法继阻,比如
f = lambda x: (x + 1 ) if x > 100 else x - 1
# 相當(dāng)于
def f(x):
return (x + 1 ) if x > 100 else x - 1;
或者把lambda表達(dá)式傳給一個(gè)方法露筒,例如:
def m(fun,x):
fun(x)
m(lambda x: print(x),1000)
由上面的示例可以看出來(lái)呐伞,lambda表達(dá)式是一段可執(zhí)行的代碼塊,可傳入方法內(nèi)執(zhí)行慎式,且可以賦給任意變量由變量執(zhí)行伶氢。
這其實(shí)也是支持函數(shù)式編程的特征踩娘,一段代碼塊可以在任何場(chǎng)所執(zhí)行惋鹅,不像java吁讨,需要借助對(duì)象這個(gè)承載來(lái)執(zhí)行代碼塊扎唾。從python的執(zhí)行方式就可以看出來(lái)瑞妇,
我們可以在一個(gè)py文件中輸入print 'hello world'
,然后就可以直接執(zhí)行輸出做瞪;但是在java中妹田,你需要先創(chuàng)建一個(gè)類(lèi)带膀,然后創(chuàng)建main方法扇救,在main方法里面執(zhí)行System.out.println("hello world")
;
可以看出來(lái)刑枝,支持函數(shù)式編程能夠減少很多的冗余代碼,但是也因?yàn)殪`活性使得代碼的風(fēng)格十分多變導(dǎo)致代碼不好維護(hù)迅腔。
Java引入lambda表達(dá)式的目的装畅,自然是想引入函數(shù)式編程的優(yōu)點(diǎn),讓代碼簡(jiǎn)潔沧烈,這也是lambda表達(dá)式的作用掠兄。
在java中怎么使用lambda表達(dá)式
既然知道了什么是lambda表達(dá)式,那么再來(lái)了解一下再Java中使用這種語(yǔ)言特性锌雀,有哪些方式又有那些限制和注意事項(xiàng)蚂夕。
函數(shù)式接口
在Java中,lambda是基于函數(shù)式接口的腋逆,也可以理解成它是替換函數(shù)式接口的實(shí)例的匿名對(duì)象婿牍,且它只能轉(zhuǎn)換成函數(shù)式接口。
那么什么是函數(shù)式接口呢惩歉?只有一個(gè)抽象方法的接口等脂,只能有一個(gè)抽象接口俏蛮,但是接口是可以包含default方法的(Java 8特性),如下的接口就是合法的:
@FunctionalInterface
interface FuncInter {
void test();
default void testdefault() {
System.out.println("hello default");
}
// void test2(); error it is not a Functional interface
}
@FunctionalInterface
interface FuncInter2 {
void test(String x);
}
@FunctionalInterface是一個(gè)標(biāo)識(shí)注解上遥,表明本接口只有一個(gè)抽象方法搏屑,如果再次添加抽象方法,會(huì)在編譯期報(bào)錯(cuò)粉楚,加上此注解可以防止誤加抽象方法睬棚。
函數(shù)式接口有了,怎么用呢解幼,先看下在java8 以前抑党,想要傳入一段代碼塊是怎么做的。
public static void main(String[] args) {
// 匿名類(lèi)的方法
FuncInter2 f = new FuncInter2() {
@Override
public void test(String x) {
// TODO Auto-generated method stub
}
};
f.test("hello");
}
如果不用匿名類(lèi)撵摆,就需要?jiǎng)?chuàng)建一個(gè)類(lèi)實(shí)現(xiàn)接口底靠,再創(chuàng)建接口實(shí)例,最后調(diào)用test方法特铝;
試用lambda表達(dá)式
既然說(shuō)lambda表達(dá)式是用來(lái)替換函數(shù)式接口的暑中,使用lambda的方法應(yīng)該就可以減少相關(guān)代碼的編寫(xiě):
@Test
public void testLambdasyntax() {
// lambda表達(dá)式是用來(lái)替換函數(shù)式接口的(匿名)實(shí)例
/*
* 無(wú)參抽象方法
*/
// 1. 單行語(yǔ)句
FuncInter fi = () -> System.out.println("hello");
fi.test();
// 2. 多行語(yǔ)句使用{}包圍
fi = () -> {
// TODO
System.out.println("line 1");
System.out.println("line 2");
};
fi.test();
/*
* 有參抽象方法
*/
FuncInter2 fi2 = (String a) -> System.out.println(a);
fi2.test("www");
// 可以推斷參數(shù)類(lèi)型可以不寫(xiě)類(lèi)型
fi2 = (a) -> System.out.println("no type " + a);
fi2.test("hello ");
// 單個(gè)參數(shù),且參數(shù)類(lèi)型可以推斷鲫剿,可以不加括號(hào)
fi2 = a -> System.out.println("no parentheses " + a);
fi2.test("hello ");
}
/**
* 把lambda傳給方法 </br>
* 實(shí)際上就是把函數(shù)式接口的匿名對(duì)象傳給方法 </br>
* 可以理解成把函數(shù)傳遞給方法鳄逾,但是在java里面實(shí)際上還是基于對(duì)象
*/
@Test
public void deliverToMethod() {
// 定義一個(gè)局部類(lèi)
class MyClassNeedInterface {
FuncInter fi = null;
MyClassNeedInterface(FuncInter fi) {
this.fi = fi;
}
void doSomeThing() {
fi.test();
}
void doSomeThing(FuncInter2 fi, String x) {
fi.test(x);
}
}
MyClassNeedInterface c = new MyClassNeedInterface(() -> System.out.println("This is from a lambda expression"));
c.doSomeThing();
String x = "a string ";
// 等價(jià)于 c.doSomeThing(a->System.out.println(a), x);
c.doSomeThing(System.out::println, x);
c.doSomeThing(a -> System.out.println(a), x);
}
}
確實(shí)簡(jiǎn)潔了很多×榱可以發(fā)現(xiàn)java中一個(gè)lambda表達(dá)式的形式是 參數(shù)雕凹、箭頭、表達(dá)式政冻,且可以根據(jù)情況省略括號(hào)或者參數(shù)類(lèi)型枚抵,定義出來(lái)的表達(dá)式相當(dāng)于一個(gè)接口的實(shí)例對(duì)象。
箭頭后面的表達(dá)式如果是多行代碼明场,使用花括號(hào)括起來(lái)汽摹。
實(shí)際上lambda表達(dá)式的用法,也就限于此類(lèi)情況(替代函數(shù)式接口的匿名實(shí)例)苦锨。
方法引用
在示例deliverToMethod
中有System.out::println
這種形式的使用逼泣,是引用了已經(jīng)存在的方法來(lái)填充接口的抽象方法,它支持三種形式:
- instance::instanceMethod
- Class::staticMethod
- Class::instanceMethod 此種方法的調(diào)用 第一個(gè)參數(shù)是實(shí)例方法的調(diào)用者(方法的目標(biāo))舟舒。
構(gòu)造器引用
和上節(jié)的方法引用類(lèi)似拉庶,只是引用的方法名是new。
閉包
閉包(closure)是計(jì)算機(jī)編程領(lǐng)域的專(zhuān)業(yè)名詞魏蔗,指可以包含自由(未綁定到特定對(duì)象)變量的代碼塊砍的,子函數(shù)可以使用父函數(shù)中的局部變量。
lambda表達(dá)式使得java擁有了閉包這個(gè)特性莺治。 簡(jiǎn)單來(lái)說(shuō)廓鞠,閉包是有數(shù)據(jù)的行為(函數(shù)、方法),是可以讀取其他函數(shù)內(nèi)部變量的函數(shù)谣旁。
lambda表達(dá)式具有代碼塊床佳,那么自由變量是什么?所謂自由變量榄审,是未綁定到特定對(duì)象的變量砌们,那么外部的局部變量肯定是自由變量;
子函數(shù)可以使用父函數(shù)中的局部變量搁进,也就是代碼塊可以訪問(wèn)外圍的局部變量浪感,還要能夠攜帶出去。
@FunctionalInterface
interface Closure {
int test(int a);
}
@Test
public void testClosure() {
final int a = 100; // 這里的final可以不加但是得建立在后面不會(huì)修改的前提上饼问,所以還是建議加上;
Closure ci = (b) -> {
System.out.println("I catch a int value of a :" + a);
return (a + b);
};
// a month later
int v = ci.test(1);
System.out.println(v);
}
在java中莱革,在lambda中訪問(wèn)的外部局部變量峻堰,必須是final變量,即引用不可修改盅视,如果是基本類(lèi)型捐名,必須是常量。
另外有一個(gè)很重要的規(guī)則闹击,lambda表達(dá)式的體和使用這個(gè)表達(dá)式的塊具有同一個(gè)作用域镶蹋。所以同名變量會(huì)命名沖突
如:
@Test
public void testSocpe() {
int a = 1000;
Closure closure = a -> a + 100; // ERROR
}
@Test
public void testSocpe() {
int b = 1000;
Closure c = a -> this.add100(a);//this的作用域是在testScope方法里面
System.out.println(c.test(b));// 1100
}
private int add100(int a) {
return a + 100;
}
什么時(shí)候使用lambda表達(dá)式
lambda表達(dá)式定義的可執(zhí)行代碼塊,如果是需要立即執(zhí)行的赏半,可以直接寫(xiě)在執(zhí)行方法里面梅忌,何必繞個(gè)圈子去調(diào)用接口的方法呢。所以lambda表達(dá)式的使用場(chǎng)景是除破,暫時(shí)不要執(zhí)行的代碼塊牧氮,在未來(lái)因?yàn)槟硞€(gè)事件觸發(fā)而執(zhí)行,所以重點(diǎn)在于延遲執(zhí)行瑰枫。
jdk提供了一系列的函數(shù)式接口踱葛,涵蓋很多使用場(chǎng)景。常用的有:
接口 | 抽象方法 | 參數(shù)類(lèi)型 | 返回值類(lèi)型 |
---|---|---|---|
Runnable | run | - | void |
Supplier<T> | get | - | T |
Consumer<T> | accept | T | void |
Predicate<T> | test | T | boolean |
- 參考文獻(xiàn)
[Java核心技術(shù) 卷I 第十版]