原文地址:http://stackoverflow.com/documentation/java/91/lambda-expressions#t=201701170111285810613
簡(jiǎn)介
Lambda表達(dá)式用一個(gè)表達(dá)式提供了一個(gè)實(shí)現(xiàn)單個(gè)接口方法(函數(shù)式接口)的簡(jiǎn)潔明了的方式。他允許你減少你必須創(chuàng)建和維護(hù)的代碼數(shù)量,它經(jīng)常被用作匿名內(nèi)部類的替代。
介紹
函數(shù)式接口
Lambdas 只能在一個(gè)僅包含一個(gè)抽象方法的函數(shù)式接口上操作。函數(shù)式接口可以有任意的default或著static方法。(為此, 函數(shù)式接口有時(shí)候是說(shuō)具有單個(gè)抽象方法的接口, 或著SAM interfaces)。
interface Foo1 {
void bar();
}
interface Foo2 {
int bar(boolean baz);
}
interface Foo3 {
String bar(Object baz, int mink);
}
interface Foo4 {
default String bar() { // default so not counted
return "baz";
}
void quux();
}
當(dāng)聲明 函數(shù)式接口時(shí)@FunctionalInterface注解可以被加上,雖然它沒有明確的作用, 但是如果一個(gè)注解被用于非函數(shù)式接口一個(gè)compiler error 將會(huì)產(chǎn)生,因此充當(dāng)一個(gè)接口不應(yīng)該被改變的提醒者。
@FunctionalInterface
interface Foo5 {
void bar();
}
@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}
@FunctionalInterface
interface Foo6 {
void bar();
boolean equals(Object obj); // overrides one of Object's method so not counted
}
相反的, 它不是一個(gè)函數(shù)式接口, 因?yàn)樗恢褂幸粋€(gè)抽象方法。
interface BadFoo {
void bar();
void quux(); // <-- Second method prevents lambda: which one should be considered as lambda?
}
它也不是一個(gè)函數(shù)式接口, 因?yàn)樗鼪]有任何方法。
interface BlankFoo2 { }
Java 8 也在Java.util.function中提供了很多基本的模版函數(shù)式接口, 例如, 內(nèi)置的接口Predicate<T>包含了一個(gè)單個(gè)方法, 輸入一個(gè)值T并且放回一個(gè)boolean。
Lambda Expressions
Lambda表達(dá)式的基本結(jié)構(gòu)是:fi將會(huì)持有一個(gè)實(shí)現(xiàn)了FunctionalInterface接口的匿名類的實(shí)例,匿名類中一個(gè)方法的定義為{System.out.println("Hello"); }。 換句話說(shuō),等價(jià)于:
FunctionalInterface fi = new FunctionalInterface() {
@Override
public void theOneMethod() {
System.out.println("Hello");
}
};
你不能在使用lambda時(shí)明確一個(gè)方法名,反而根本不需要,因?yàn)楹瘮?shù)式接口必須有一個(gè)抽象方法, 所以java重寫了它。
一旦lambda的類型不確定,(e.g. 重寫方法)你可以給lambda添加一個(gè)強(qiáng)轉(zhuǎn)型告訴編譯器它的類型,就像:
Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true
如果函數(shù)式接口的單個(gè)方法包含參數(shù),它們的本地變量名應(yīng)該出現(xiàn)在lambda的方括號(hào)中。沒有必要去聲明參數(shù)的類型或著返回值的類型,因?yàn)樗麄兡軓慕涌诙x中推理得出(當(dāng)然如果你想聲明參數(shù)類型, 這也不是一個(gè)錯(cuò)誤)。因此,如下兩個(gè)樣例是等價(jià)的:
Foo2 longFoo = new Foo2() {
@Override
public int bar(boolean baz) {
return baz ? 1 : 0;
}
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };
如果函數(shù)只有一個(gè)參數(shù),參數(shù)兩邊的圓括號(hào)可以省略:
Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay
隱式返回
如果被放在lambda中的代碼是一個(gè)java 表達(dá)式而不是一個(gè)聲明,它就會(huì)被當(dāng)作一個(gè)返回這個(gè)表達(dá)式值的方法,因此,下面這兩個(gè)是等價(jià)的:
IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };
訪問(wèn)本地變量(值閉包)
因?yàn)閘ambdas 是匿名內(nèi)部類的簡(jiǎn)化寫法,它們遵循在一個(gè)閉合的域中訪問(wèn)本地變量相同的規(guī)則;變量必須被當(dāng)作final并且在lambda表達(dá)式中不能夠被修改。
IntUnaryOperator makeAdder(int amount) {
return (x) -> (x + amount); // Legal even though amount will go out of scope
// because amount is not modified
}
IntUnaryOperator makeAccumulator(int value) {
return (x) -> { value += x; return value; }; // Will not compile
}
如果以這種方式包含一個(gè)可改變的變量是必要的, 一個(gè)包含此變量的拷貝的合法對(duì)象應(yīng)該被使用, Read more in Closures with lambda expressions.
接收l(shuí)ambdas
因?yàn)閘ambda是一個(gè)接口的實(shí)現(xiàn),去使一個(gè)方法接收l(shuí)ambda并沒有什么特別的要做:任何函數(shù)只要是函數(shù)式接口都能夠接收一個(gè)lambda。
public void passMeALambda(Foo1 f) {
f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));
使用lambda表達(dá)式去排序一個(gè)集合
在java 8 之前, 當(dāng)排序一個(gè)集合的時(shí)候, 用一個(gè)匿名(或著 有名字)類去實(shí)現(xiàn)java.util.Comparator接口是必要的:
Java SE 1.2
Collections.sort(
personList,
new Comparator<Person>() {
public int compare(Person p1, Person p2){
return p1.getFirstName().compareTo(p2.getFirstName());
}
}
);
從java 8 開始, 匿名內(nèi)部類能夠被lambda表達(dá)式替代, 注意到p1和p2參數(shù)能夠被忽略, 因?yàn)榫幾g器能夠自動(dòng)的推斷出它們。
Collections.sort(
personList,
(p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);
這個(gè)例子能夠被簡(jiǎn)化通過(guò)使用Comparator.comparing
和method references(方法引用), 用::(雙冒號(hào))符號(hào)來(lái)表達(dá):
Collections.sort(
personList,
Comparator.comparing(Person::getFirstName)
);
靜態(tài)導(dǎo)入允許我們更加簡(jiǎn)明的去表達(dá)它, 但是對(duì)于是否能夠提高整體可讀性是備受爭(zhēng)論的。
import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(personList, comparing(Person::getFirstName));
Comparators構(gòu)建這種方式可以用來(lái)鏈?zhǔn)秸{(diào)用。例如, 通過(guò)名字比較之后, 如果有一些人具有相同的名字, 那么thenComparing方法將會(huì)根據(jù)性別來(lái)接著比較
sort(personList, comparing(Person::getFirstName).thenComparing(Person::getLastName));
方法引用
方法引用允許提前定義的靜態(tài)或著實(shí)例方法去綁定到一個(gè)合適的函數(shù)式接口來(lái)當(dāng)作參數(shù)傳遞,而不是用一個(gè)匿名的lambda表達(dá)式。
假設(shè)我們有一個(gè)模型:
class Person {
private final String name;
private final String surname;
public Person(String name, String surname){
this.name = name;
this.surname = surname;
}
public String getName(){ return name; }
public String getSurname(){ return surname; }
}
List<Person> people = getSomePeople();
實(shí)例方法引用(對(duì)于一個(gè)任意的實(shí)例)
people.stream().map(Person::getName)
等價(jià)的lambda:
people.stream().map(person -> person.getName())
在這個(gè)例子中, 對(duì)于一個(gè)Person類的實(shí)例方法getName()的一個(gè)方法引用被傳遞。因?yàn)樗划?dāng)作一個(gè)集合類型, 實(shí)例上的方法(之后被察覺)將會(huì)被調(diào)用 。
實(shí)例方法引用(對(duì)于一個(gè)特定類型)
people.forEach(System.out::println);
因?yàn)镾ystem.out是一個(gè)PrintStream的實(shí)例,對(duì)這個(gè)特定的實(shí)例的一個(gè)方法引用被當(dāng)作一個(gè)參數(shù)傳遞。等價(jià)的lambda表達(dá)式:
people.forEach(person -> System.out.println(person));
靜態(tài)的方法引用
對(duì)于轉(zhuǎn)換流,我們能夠使用靜態(tài)方法引用
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)
這個(gè)例子傳遞了一個(gè)String類型的valueOf()靜態(tài)方法引用, 因此valueOf()
在集合中的實(shí)例對(duì)象中被當(dāng)做一個(gè)參數(shù)傳遞。等價(jià)的lambda:
numbers.stream().map(num -> String.valueOf(num))
構(gòu)造器引用
List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)
讀Collect Elements of a Stream into a Collection看看如何收集元素到集合中。唯一的一個(gè)Integer的String參數(shù)構(gòu)造器在這里被使用, 通過(guò)一個(gè)被當(dāng)作參數(shù)提供的String來(lái)構(gòu)造一個(gè)整數(shù),只要這個(gè)string代表一個(gè)數(shù)字, 流將會(huì)被轉(zhuǎn)化為整數(shù)。等價(jià)的lambda:
Collect Elements of a Stream into a Collection
備忘單
方法參考格式 | 代碼 | 等價(jià)于 |
---|---|---|
Static method | TypeName::method | (args) -> TypeName.method(args) |
Non-static method (from instance) | instance::method | (args) -> instance.method(args |
Non-static method (no instance) | TypeName::method | (instance, args) -> instance.method(args) |
Constructor | TypeName::new | (args) -> new TypeName(args) |
實(shí)現(xiàn)多個(gè)接口
有時(shí)候你想使lambda表達(dá)式實(shí)現(xiàn)多個(gè)接口,使用標(biāo)記式接口(例如java.io.Serializable)是很有用的, 因?yàn)樗麄儾惶砑尤魏纬橄蠓椒ā@缒阆胧褂靡粋€(gè)客戶自定義Comparator創(chuàng)建一個(gè)TreeSet, 接著序列化它, 并通過(guò)網(wǎng)路發(fā)送它 。一般方法:
TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));
并不生效, 因?yàn)閷?duì)Comparator的lambda沒有實(shí)現(xiàn)Serialization
, 你能夠修正它通過(guò)使用交叉類型, 并且顯式的明確這個(gè)lambda是需要序列化的:
TreeSet<Long> ts = new TreeSet<>(
(Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));
如果你平凡的使用交叉類型(例如, 你正在使用一個(gè)譬如幾乎所有東西都必須序列化的Apache Spark 框架), 你能夠創(chuàng)建一個(gè)空的接口并在你的代碼中使用它們。
public interface SerializableComparator extends Comparator<Long>, Serializable {}
public class CustomTreeSet {
public CustomTreeSet(SerializableComparator comparator) {}
}
這樣你就保證了傳遞的comparator接口將會(huì)被序列化。
lambda表達(dá)式閉包
當(dāng)一個(gè)lambda表達(dá)式引用一個(gè)封閉域(全局或局部)內(nèi)的變量, 一個(gè)lambda閉包被創(chuàng)建。這樣做的規(guī)則和內(nèi)聯(lián)方法以及匿名類是相同的。來(lái)自一個(gè)閉合域中的本地變量在一個(gè)lambda內(nèi)部被使用時(shí)必須是final。在java 8 (最早的支持lambdas的版本)中不需要在外部上下文中聲明final, 但是必須(當(dāng)作final)來(lái)對(duì)待。例如:
int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
只要值n變量沒有被改變,它就是合法的。如果你嘗試去在lambda的外部或內(nèi)部去改變這個(gè)變量, 你將會(huì)得到下面的編譯錯(cuò)誤:
“l(fā)ocal variables referenced from a lambda expression must be final oreffectively final”.
例如:如果在lambda里面必須使用一個(gè)可改變的變量, 正常的方法是聲明一個(gè)對(duì)此變量的final拷貝,然后使用這個(gè)拷貝。例如:
int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = k;
// do something
};
n++; // Now will not generate an error
r.run(); // Will run with i = 0 because k was 0 when the lambda was created
自然地, lambda的body體里面對(duì)原始變量的改變是不可見的。
注意到j(luò)ava 不支持真正的閉包, 一個(gè)java lambda不能夠以一種能夠看到在它所被實(shí)例化的環(huán)境中的變量的改變的方式被創(chuàng)建 。如果你想實(shí)現(xiàn)一個(gè)能夠?qū)λ沫h(huán)境進(jìn)行觀察或做出改變的閉包, 你應(yīng)該使用一個(gè)合法的類“聚集”它。例如:
// Does not compile ...
public IntUnaryOperator createAccumulator() {
int value = 0;
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
以上將不會(huì)被編譯,由于之前討論的原因。我們能夠繞過(guò)編譯錯(cuò)誤,如下:
// Does not compile ...
public IntUnaryOperator createAccumulator() {
int value = 0;
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
這個(gè)問(wèn)題是IntUnaryOperator接口設(shè)計(jì)契約的打破, 它聲明實(shí)例應(yīng)該是函數(shù)式的并且無(wú)狀態(tài)的。如果一個(gè)閉包被傳遞進(jìn)一個(gè)可以接收函數(shù)式對(duì)象的內(nèi)置函數(shù)式接口, 這是很容易造成沖突和錯(cuò)誤的行為。解封裝的易變狀態(tài)的閉包應(yīng)當(dāng)被實(shí)現(xiàn)為一個(gè)合法類。例如:
// Correct ...
public class Accumulator {
private int value = 0;
public int accumulate(int x) {
value += x;
return value;
}
}
Lambda - Listener 示例
匿名類listener
JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button was pressed");
}
});
Lambda listener
JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
System.out.println("Button was pressed");
});
在你的函數(shù)式接口中使用Lambda
Lambda 意味著為單個(gè)方法的接口提供一個(gè)內(nèi)聯(lián)實(shí)現(xiàn)代碼和用一種常規(guī)變量的方式來(lái)傳遞它們的能力,正如我們所曾做的。我們把它叫做函數(shù)式接口。
例如, 用一個(gè)匿名類來(lái)寫一個(gè)Runnable, 然后啟動(dòng)一個(gè)線程,就像這樣:
//Old way
new Thread(
new Runnable(){
public void run(){
System.out.println("run logic...");
}
}
).start();
//lambdas, from Java 8
new Thread(
()-> System.out.println("run logic...")
).start();
現(xiàn)在, 和上面一致, 你們有一些客戶端接口:
interface TwoArgInterface {
int operate(int a, int b);
}
在你的代碼中你怎么使用Lambda去給出這個(gè)接口的實(shí)現(xiàn)? 和上述
public class CustomLambda {
public static void main(String[] args) {
TwoArgInterface plusOperation = (a, b) -> a + b;
TwoArgInterface divideOperation = (a,b)->{
if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
return a/b;
};
System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));
}
}
return 僅僅從Lambda中返回, 而不是外部方法
當(dāng)心這不同于Scala和Kotlin!
void threeTimes(IntConsumer r) {
for (int i = 0; i < 3; i++) {
r.accept(i);
}
}
void demo() {
threeTimes(i -> {
System.out.println(i);
return; // Return from lambda to threeTimes only!
});
}
當(dāng)嘗試用特有語(yǔ)言結(jié)構(gòu), 這會(huì)導(dǎo)致無(wú)法預(yù)期的異常, 譬如內(nèi)置的結(jié)構(gòu):for
循環(huán)return表現(xiàn)的不同:
void demo2() {
for (int i = 0; i < 3; i++) {
System.out.println(i);
return; // Return from 'demo2' entirely
}
}
在scala 和 Kotlin ,demo和demo2都僅僅打印0, 但這并不是始終如一的, java方法和refactoring和 類的使用是一致的 -return在代碼的頂部和底部表現(xiàn)相同:
void demo3() {
threeTimes(new MyIntConsumer());
}
class MyIntConsumer implements IntConsumer {
public void accept(int i) {
System.out.println(i);
return;
}
}
因此, java 的return和類方法和refactoring更為一致, 但和內(nèi)置的for、while不具一致性, 保留了它們的特殊性。由此, 解析來(lái)兩個(gè)案例在java 中是等價(jià)的:
IntStream.range(1, 4)
.map(x -> x * x)
.forEach(System.out::println);
IntStream.range(1, 4)
.map(x -> { return x * x; })
.forEach(System.out::println);
此外,try-with-resources的使用在java中是安全的:
class Resource implements AutoCloseable {
public void close() { System.out.println("close()"); }
}
void executeAround(Consumer<Resource> f) {
try (Resource r = new Resource()) {
System.out.print("before ");
f.accept(r);
System.out.print("after ");
}
}
void demo4() {
executeAround(r -> {
System.out.print("accept() ");
return; // Does not return from demo4, but frees the resource.
});
}
將會(huì)打印before accept() after close()。 在Scala 和Kotlin 語(yǔ)義中try-with-resources將不會(huì)被關(guān)閉, 將僅僅打印出before accept()。
Lambdas 和 執(zhí)行-環(huán)繞模式
在一些簡(jiǎn)單的場(chǎng)景中, 作為函數(shù)式接口, 有一些使用lambdas好的樣例, 一個(gè)相對(duì)常見的能夠被lambdas所增強(qiáng)的用例是被稱為Execute-Around模式, 在這個(gè)模式中, 你有一組標(biāo)準(zhǔn)的setup/teardown 代碼, 很多場(chǎng)景需要被用例特定的代碼去環(huán)繞, 一些通用的示例就是file io , database io , try / catch 代碼塊。
interface DataProcessor {
void process( Connection connection ) throws SQLException;;
}
public void doProcessing( DataProcessor processor ) throws SQLException{
try (Connection connection = DBUtil.getDatabaseConnection();) {
processor.process(connection);
connection.commit();
}
}
接著用lambda 來(lái)調(diào)用這個(gè)方法,看起來(lái)像下面這樣:
public static void updateMyDAO(MyVO vo) throws DatabaseException {
doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}
它并不限于I/O操作, 它能夠應(yīng)用于和setup/tear down類似且變量較少的任務(wù)的任何場(chǎng)景。這種模式的主要好處是代碼重用 和 強(qiáng)制DRY(Don’t Repeat Yourself)。
傳統(tǒng)方式 -> Lambda風(fēng)格
Traditional way
interface MathOperation{
boolean unaryOperation(int num);
}
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = new MathOperation() {
@Override
public boolean unaryOperation(int num) {
return num%2 == 0;
}
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
Lambda style
1、移除類名和函數(shù)式接口體
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = (int num) -> {
return num%2 == 0;
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
2、可選的類型的聲明
MathOperation isEven = (num) -> {
return num%2 == 0;
};
3、可選的參數(shù)兩邊括弧, 如果是一個(gè)參數(shù)
MathOperation isEven = num -> {
return num%2 == 0;
};
4、可選的花括號(hào), 如果在函數(shù)體中只有一行
5、可選的返回值, 如果在函數(shù)體中只有一行
MathOperation isEven = num -> num%2 == 0;
Lambdas 與內(nèi)存利用
因?yàn)镴ava lambda是閉包的, 它們能夠 “捕獲” 在閉合作用域中變量的值, 然而并不是所有的lambda都能捕獲 – 簡(jiǎn)單的lambdas 就像s -> s.length()
什么都沒有捕獲, 被稱作 無(wú)狀態(tài)的 – 捕獲形lambdas 要求一個(gè)臨時(shí)的對(duì)象去持有這個(gè)被捕獲的變量, 在這個(gè)代碼片中, 這個(gè)lambda() -> j是一個(gè)捕獲型lambda, 并且在被使用時(shí)可能造成一個(gè)對(duì)象被分配內(nèi)存。
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000000; i++) {
int j = i;
doSomethingWithLambda(() -> j);
}
}
雖然并不會(huì)很快的變得顯而易見, 因?yàn)閚ew關(guān)鍵字并沒有在任何地方出現(xiàn), 但是這個(gè)代碼創(chuàng)建了1000000000 個(gè)獨(dú)立的 () -> j lambda實(shí)例。
使用lambda條件表達(dá)式來(lái)從列表中獲取某些值
從java 8 開始, 你能夠使用lambda 表達(dá)式 & predicates。例如: 使用lambda 表達(dá)式 & predicates 從列表中獲取某個(gè)值, 在這個(gè)樣例中, 如果他們具有大于18歲的事實(shí)就會(huì)被打印出來(lái), 反之不會(huì)。
Person Class:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
}
內(nèi)置的來(lái)自java.util.function.Predicate 包中的接口Predicate是一個(gè)函數(shù)式接口,并有一個(gè)boolean test(T t)
方法。示例用法:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class LambdaExample {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Jeroen", 20));
personList.add(new Person("Jack", 5));
personList.add(new Person("Lisa", 19));
print(personList, p -> p.getAge() >= 18);
}
private static void print(List<Person> personList, Predicate<Person> checker) {
for (Person person : personList) {
if (checker.test(person)) {
System.out.print(person + " matches your expression.");
} else {
System.out.println(person + " doesn't match your expression.");
}
}
}
}
這個(gè)print(personList, p -> p.getAge() >= 18);方法采用一個(gè)lambda表達(dá)式(因?yàn)镻redicate 被用于作為一個(gè)參數(shù)), 你能定義自己所需要的表達(dá)式, checker的test方法檢查表達(dá)式正確與否:checker.test(person)。你可以輕易的去把它變成其他的, 例如print(personList, p -> p.getName().startsWith("J")); 它會(huì)檢查人的名字是否以字母“J”開頭。