1. 程式人生 > >參考《Java瘋狂講義》

參考《Java瘋狂講義》

main cat ons 返回值 元素 oid any parallel 簡潔

參考《Java瘋狂講義》
Lambda表達式支持將代碼塊作為方法參數,Lambda表達式允許使用更簡潔的代碼來創建只有一個抽象方法的接口(這種接口被稱為函數式接口)的實例

1. Lambda表達式入門

下面先使用匿名內部類來改寫(6.6介紹的命令模式Command表達式的例子)

public class CommandTest
{
public static void main(String[] args)
{
ProcessArray pa = new ProcessArray();
int[] array = {3, -4, 6, 4};
// 處理數組,具體處理行為取決於匿名內部類
pa.process(array , new Command()
{
public void process(int[] target)
{
int sum = 0;
for (int tmp : target )
{
sum += tmp;
}
System.out.println("數組元素的總和是:" + sum);
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
程序通過匿名內部類實例來封裝處理行為。而Lambda表達式完全可以簡化創建匿名內部類對象。

public class CommandTest2
{
public static void main(String[] args)
{
ProcessArray pa = new ProcessArray();
int[] array = {3, -4, 6, 4};
// 處理數組,具體處理行為取決於匿名內部類
pa.process(array , (int[] target)->{
int sum = 0;
for (int tmp : target )
{
sum += tmp;
}
System.out.println("數組元素的總和是:" + sum);
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
通過上面的對比可知;Lambda表達式主要就是代替匿名內部類的繁瑣語法。不需要new Xxx(){}的語法,不需要指出重寫方法的名字,也不需要給出重寫方法的返回值類型——只需要給出重寫方法的形參列表即可。
當使用Lambda表達式代替匿名內部類創建對象時,Lambda表達式的代碼塊將會替代實現抽象方法的方法體,Lambda表達式就相當於一個匿名方法。
他由三部分組成;

形參列表。形參列表允許省略形參類型。如果形參列表中只有一個參數,甚至連形參列表的括號都能省略。
箭頭。英文劃線和大於號組成。
代碼塊。如果代碼塊只有一條語句,Lambda表達式允許省略代碼塊的花括號,那麽這條語句就不要用花括號表示語句的結束。Lambda代碼塊只有一條return語句,甚至可以省略return關鍵字。Lambda表達式需要返回值,而他的代碼塊中僅有一條省略了return的語句,Lambda表達式會自動返回該語句的值。
下面示範Lambda表達式的幾種簡單寫法;

interface Eatable
{
void taste();
}
interface Flyable
{
void fly(String weather);
}
interface Addable
{
int add(int a , int b);
}
public class LambdaQs
{
// 調用該方法需要Eatable對象
public void eat(Eatable e)
{
System.out.println(e);
e.taste();
}
// 調用該方法需要Flyable對象
public void drive(Flyable f)
{
System.out.println("我正在駕駛" + f);
f.fly("【碧空如洗的晴日】");
}
// 調用該方法需要Addable對象
public void test(Addable add)
{
System.out.println("5與3的和為" + add.add(5, 3));
}
public static void main(String[] args)
{
LambdaQs lq = new LambdaQs();
// Lambda表達式的代碼塊只有一條語句,可以省略花括號
lq.eat(()-> System.out.println("蘋果的味道不錯"));
// Lambda表達式的形參列表只有一個形參,可以省略圓括號
lq.drive(weather ->
{
System.out.println("今天天氣是" + weather);
System.out.println("?直升機飛行平穩");
});
//Lambda表達式的代碼塊只有一條語句,可以省略花括號
// 代碼塊只有一條語句,即使該表達式需要返回值,也可以省略return關鍵字
lq.test((a , b)->a + b);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2. Lambda表達式與函數式接口
Lambda表達式的類型,也被稱為“目標類型(target type)”,Lambda表達式的目標類型必須是“函數式接口(functional interface)”。函數式接口代表只包含一個抽象方法的接口。函數式接口可以有多個默認方法,類方法,但只能聲明一個抽象方法。

Java 8 專門為函數式接口提供了@FunctionalInterface註解,該註解通常放在接口定義前面,該註解對程序功能沒有任何作用,它用於告訴編譯器執行更嚴格的檢查——檢查該接口必須是函數式接口,否者編譯器報錯。

@FunctionalInterface
interface FkTest
{
void run();
}

public class LambdaTest
{
public static void main(String[] args)
{
// Runnable接口中只包含一個無參數的方法
// Lambda表達式代表的匿名方法實現了Runnable接口中唯一的,無參數的方法
// 因此下面的Lambda表達式創建了一個Runnable對象
Runnable r = () -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
// 下面代碼將出現錯誤:Object不是函數式接口
//Object obj = (www.michenggw.com) -> {
// for(int i = www.douniu157.com 0 ; i < 100 ; i ++)
// {
// System.out.println();
// }
//};
//對Lambda表達式進行強制類型轉換,這樣就可以確定該表達式的目標類型為Runnable函數式接口
Object obj1 = (Runnable)(www.dfgjpt.com) -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
//同樣的Lambda表達式可以被當成不同的目標類型,唯一的要求是:
// Lambda表達式的參數列表與函數式接口中唯一抽象方法的形參列表相同?
Object obj2 = (FkTest)() -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};


從以上代碼可以得出一些結論:
Lambda表達式實現的是匿名方法——因此它只能實現特定的函數接口中唯一的方法。這就意味著Lambda表達式有如下兩個限制:(1)Lambda表達式的目標類型只能是明確的函數式接口。(2)Lambda表達式只能為函數式接口創建對象。
為了保證Lambda表達式的目標類型是一個明確的函數式接口,可以有如下三種方式:(1)將Lambda表達式賦值給函數式接口類型的變量(2)將Lambda表達式作為函數式接口類型的參數傳給某個方法(3)使用函數式接口對Lambda表達式進行強制類型轉換
Java 8 在java.util.function包下預定了大量函數式接口,典型有4類接口:

XxxFunction:這類接口中通常包含一個apply()抽象方法,該方法對參數進行處理(處理邏輯由Lambda表達式決定),然後返回一個新的值
XxxConsumer:這類接口中通常包含一個accept()抽象方法,該方法與上面的方法相似,只是沒有返回值
XxxPredicate:這類接口中通常包含一個test()抽象方法,該方法用於對參數進行某種判斷(由Lambda表達式決定),然後返回一個Boolean類型的值
XxxSupplier:這類接口中通常包含一個getAsXxx()抽象方法,該方法沒有參數,會按某種邏輯算法(由Lambda表達式決定)返回一個數據
3. 方法引用與構造器引用
如果Lambda表達式的代碼塊只有一條代碼,不僅可以省略花括號,還可以在代碼塊中使用方法引用和構造器引用。
方法引用和構造器引用可以讓Lambda表達式的代碼塊更加簡潔,方法引用和代碼塊引用都要使用兩個英文冒號。

引用類方法
@FunctionalInterface
interface Converter{
Integer convert(String from);
}
/下面代碼使用Lambda表達式創建Converter對象
Converter converter1 www.dasheng178.com= from -> Integer.valueOf(from);
Integer val =www.meiwanyule.cn converter1.convert("99");
System.out.println(val); // 將輸出99

使用一行調用類方法進行替換

// 方法引用代替Lambda表達式:引用類方法
// 函數式接口中被實現的全部參數傳給該類方法作為參數
Converter converter1 = Integer::valueOf;
1
2
3
引用特定對象的實例方法
// 下面代碼使用Lambda表達式創建Converter對象
Converter converter2 = from -> "fkit.org".indexOf(from);
1
2
Integer value = converter2.convert("it");
System.out.println(value); // 輸出2
1
2
// 方法引用代替Lambda表達式,引用特定對象的實例方法
// 函數式接口中被實現的全部參數傳給該方法作為參數
Converter converter2 = "fkit.org"::indexOf;
1
2
3
3.引用某類對象的實例方法

@FunctionalInterface
interface MyTest
{
String test(String a , int b , int c);
}
//下面代碼使用Lambda表達式創建MyTest對象
MyTest mt = (a , b , c) -> a.substring(b , c);

String str = mt.test("Java I Love you" , 2 , 9);
System.out.println(str); // 輸出:va I Lo
// 方法引用代替Lambda表達式,引用某類對象的實例方法
// 函數式接口中被實現的第一個參數作為調用者
//後面的參數全部傳遞給該方法作為參數
MyTest mt = String::substring;
引用構造器
@FunctionalInterface
interface YourTest
{
JFrame win(String title);
}
// 下面代碼使用Lambda表達式創建YourTest對象
YourTest yt = (String a) -> new JFrame(a);

JFrame jf = yt.win("我的窗口");
System.out.println(jf);

//構造器引用代替Lambda表達式
// 函數式接口中被實現的全部參數傳給該構造器作為參數
YourTest yt = JFrame::new;

4. Lambda表達式與匿名內部類的聯系與區別
Lambda表達式是匿名內部類的一種簡化
相同點:

Lambda表達式與匿名內部類一樣,都可以直接訪問“effectively final”的局部變量,以及外部類的成員變量。
Lambda表達式與匿名內部類創建的對象一樣,都可以直接調用從接口中繼承的默認方法
不同之處:
匿名內部類可以為任何接口創建實例——不管接口包含多少個抽象方法,只要匿名內部類實現了所有抽象方法即可;但 Lambda表達式只能為函數式接口創建對象
匿名內部類可以為抽象類甚至普通類創建對象
匿名內部類實現的抽象方法的方法體允許調用接口中的默認方法;但 Lambda表達式的代碼塊不允許調用接口中的默認方法
5. 使用 Lambda表達式調用Arrays的類方法

public class LambdaArrays
{
public static void main(String[] args)
{
String[] arr1 = new String[]{"java" , "fkava" , "fkit", "ios" , "android"};
Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
System.out.println(Arrays.toString(arr1));
int[] arr2 = new int[]{3, -4 , 25, 16, 30, 18};
// left代表素組中前一個索引處的元素,計算第一個元素時,left為1
// right代表素組中當前索引處的元素
Arrays.parallelPrefix(arr2, (left, right)-> left * right);
System.out.println(Arrays.toString(arr2));
long[] arr3 = new long[5];
// operand代表正在計算的元素索引
Arrays.parallelSetAll(arr3 , operand -> operand * 5);
System.out.println(Arrays.toString(arr3));
}

參考《Java瘋狂講義》