函數語言程式設計--使用lambda表示式
阿新 • • 發佈:2019-01-31
前面一篇部落格我們已經說到了,lambda表示式允許使用更簡潔的程式碼來建立只有一個抽象方法的介面的例項。現在我們來寫一段java的命令者模式來自己研究下lambda表示式的語法。
這裡重複下命令者模式:
考慮這麼一個情景,某個方法需要完成一個行為,但是這個行為的具體實現無法確定,必須等到執行該方法時才可以確定。舉個例子,我有個方法需要遍歷某個陣列的陣列元素,但是無法確定在遍歷這個陣列元素時如何處理這些元素,需要在實際呼叫該方法時候指定具體的處理行為。也就是說我現在要處理一些引數,但是具體的這些引數的處理方式我不確定,我想要寫一個方法,即可以傳入一些引數,也可以傳入這些引數的處理方式,怎麼辦呢?使用命令者模 式,因為java8之前java不允許傳一段程式碼進一個方法。
下面是原來的程式碼:
現在我們使用lambda表示式改一下上面的程式碼:
這裡的程式碼可以正常的編譯和執行,說明lambda表示式實際上將會被當成一個任意型別的物件,到底需要當成何種類型的物件,這取決於執行環境的需要。下一篇部落格我會整理到函式式介面,到時候就清楚了。 在上面的使用過程中,我們看到了: 1),我們在使用lambda表示式的時候,引數的型別可以被推匯出來,當然我們也可以自己新增上引數的型別或者注解或者其他的修飾符。如果需要顯式指定一個引數的型別,那麼必須為所有的引數宣告型別。比如: (int a, int b)->(a%b)==0是合法的 (int a, b)->(a%b)==0是不合法的 2),永遠不需要為一個lambda表示式執行返回型別,它總是可以從上下文中被推匯出來。比如: (String first,String second)->Integer.compare(first.length(),second.length());可以被使用在期望結果型別是int的上下文中。 3),在lambda表示式中,只在某些分支中返回值,其他分支沒有返回值是不合法的。比如下面的程式碼:
4),為了在目標型別上下文中使用lambda表示式,抽象方法的型別和表示式的型別必須相容。具體來說,lambda表達式的引數的型別和數量必須與方法的引數相容,返回型別必須相容,並且lambda表示式可能丟擲的異常也必須能被方法接受。 5),lambda表示式的方法體與巢狀程式碼塊有著相同的作用域。因此他也適用同樣的命名衝突和遮蔽規則。在lambda表示式中不允許宣告一個與區域性變數同名的引數或者區域性變數。比如下面程式碼報錯:
public class Test { //處理陣列 public void process(int[] array, Command cmd) { cmd.process(array); } public static void main(String[] args) { Test test = new Test(); int[] target = { 1, 2, 3 }; test.process(target, new Command() { @Override public void process(int[] array) { for (int i : target) { System.out.println(i); } } }); } } interface Command { //處理陣列的行為 void process(int[] array); }
現在我們使用lambda表示式改一下上面的程式碼:
從上面的程式碼我們可以看出,我們使用lambda表示式不需要再寫new XXX(){}這種繁瑣的程式碼了呢,現在不需要指出重寫方法的名字,也不需要給出重寫的方法的返回值型別,只要給出重寫的方法括號以及括號裡面的形參列表就可以。其實這裡也說出lambda表示式的主要作用,就是代替匿名內部類的繁瑣語法。 lambda表示式有三部分組成: 1),形參列表。形參列表允許省略形參型別,如果形參只有一個,甚至連新參列表的圓括號也可以省略。 2),箭頭。->。 3),程式碼塊。如果程式碼塊只包含一條語句,可以省略花括號。如果程式碼塊只有一條return語句,可以省略return關鍵字。如果lambda表示式需要返回值,而它的程式碼塊中也僅有一條省略了return的語句,lambda表示式會自動返回這條語句的值。 下面實際編碼來演示下lambda表示式的語法:public class Test { //處理陣列 public void process(int[] array, Command cmd) { cmd.process(array); } public static void main(String[] args) { Test test = new Test(); int[] target = { 1, 2, 3 }; test.process(target, (int[] array) -> { for (int i : target) { System.out.println(i); } }); } } @FunctionalInterface interface Command { //處理陣列的行為 void process(int[] array); }
public class Test
{
public void testA(A a)
{
System.out.println(a);
a.test();
}
public void testB(B b)
{
System.out.println(b);
b.test("111");
}
public void testC(C c)
{
System.out.println(c);
System.out.println("這裡是介面C的表示式:" + c.test(1, 2));
}
public static void main(String[] args)
{
Test test = new Test();
test.testA(() ->
{
System.out.println("這裡是介面A的lambda表示式");
});
//上面的程式碼塊只有一行程式碼,可以簡寫
test.testA(() -> System.out.println("這裡是介面A的lambda表示式"));
test.testB((str) -> System.out.println("這裡是介面B的lambda表示式" + str));
//上面的形參引數只有一個,可以簡寫
test.testB(str -> System.out.println("這裡是介面B的lambda表示式" + str));
test.testC((a, b) ->
{
return (a + b);
});
//上面的程式碼塊只有一行程式碼,可以省略花括號,也可以省略return語句
test.testC((a, b) -> a + b);
}
}
@FunctionalInterface
interface A
{
void test();
}
@FunctionalInterface
interface B
{
void test(String str);
}
@FunctionalInterface
interface C
{
int test(int a, int b);
}
這裡的程式碼可以正常的編譯和執行,說明lambda表示式實際上將會被當成一個任意型別的物件,到底需要當成何種類型的物件,這取決於執行環境的需要。下一篇部落格我會整理到函式式介面,到時候就清楚了。 在上面的使用過程中,我們看到了: 1),我們在使用lambda表示式的時候,引數的型別可以被推匯出來,當然我們也可以自己新增上引數的型別或者注解或者其他的修飾符。如果需要顯式指定一個引數的型別,那麼必須為所有的引數宣告型別。比如: (int a, int b)->(a%b)==0是合法的 (int a, b)->(a%b)==0是不合法的 2),永遠不需要為一個lambda表示式執行返回型別,它總是可以從上下文中被推匯出來。比如: (String first,String second)->Integer.compare(first.length(),second.length());可以被使用在期望結果型別是int的上下文中。 3),在lambda表示式中,只在某些分支中返回值,其他分支沒有返回值是不合法的。比如下面的程式碼:
public class Test
{
public static void main(String[] args)
{
A a = (b) ->
{
if (b > 0)
{
return 1;
}
};
}
}
@FunctionalInterface
interface A
{
int test(int a);
}
4),為了在目標型別上下文中使用lambda表示式,抽象方法的型別和表示式的型別必須相容。具體來說,lambda表達式的引數的型別和數量必須與方法的引數相容,返回型別必須相容,並且lambda表示式可能丟擲的異常也必須能被方法接受。 5),lambda表示式的方法體與巢狀程式碼塊有著相同的作用域。因此他也適用同樣的命名衝突和遮蔽規則。在lambda表示式中不允許宣告一個與區域性變數同名的引數或者區域性變數。比如下面程式碼報錯:
int a;
test.testC((a, b) ->a + b);