Java 8新增Lambda表示式(6.8)
參考《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); } }); } }
程式通過匿名內部類例項來封裝處理行為。而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); }); } }
通過上面的對比可知;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);
}
}
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 = () -> {
// for(int i = 0 ; i < 100 ; i ++)
// {
// System.out.println();
// }
//};
//對Lambda表示式進行強制型別轉換,這樣就可以確定該表示式的目標型別為Runnable函式式介面
Object obj1 = (Runnable)() -> {
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 = from -> Integer.valueOf(from);
Integer val = converter1.convert("99");
System.out.println(val); // 將輸出99
使用一行呼叫類方法進行替換
// 方法引用代替Lambda表示式:引用類方法
// 函式式介面中被實現的全部引數傳給該類方法作為引數
Converter converter1 = Integer::valueOf;
- 引用特定物件的例項方法
// 下面程式碼使用Lambda表示式建立Converter物件
Converter converter2 = from -> "fkit.org".indexOf(from);
Integer value = converter2.convert("it");
System.out.println(value); // 輸出2
// 方法引用代替Lambda表示式,引用特定物件的例項方法
// 函式式介面中被實現的全部引數傳給該方法作為引數
Converter converter2 = "fkit.org"::indexOf;
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));
}
}