1. 程式人生 > >Java 8新增Lambda表示式(6.8)

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類介面:

  1. XxxFunction:這類介面中通常包含一個apply()抽象方法,該方法對引數進行處理(處理邏輯由Lambda表示式決定),然後返回一個新的值
  2. XxxConsumer:這類介面中通常包含一個accept()抽象方法,該方法與上面的方法相似,只是沒有返回值
  3. XxxPredicate:這類介面中通常包含一個test()抽象方法,該方法用於對引數進行某種判斷(由Lambda表示式決定),然後返回一個Boolean型別的值
  4. XxxSupplier:這類介面中通常包含一個getAsXxx()抽象方法,該方法沒有引數,會按某種邏輯演算法(由Lambda表示式決定)返回一個數據

3. 方法引用與構造器引用
如果Lambda表示式的程式碼塊只有一條程式碼,不僅可以省略花括號,還可以在程式碼塊中使用方法引用和構造器引用。
方法引用和構造器引用可以讓Lambda表示式的程式碼塊更加簡潔,方法引用和程式碼塊引用都要使用兩個英文冒號。

  1. 引用類方法
@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;
  1. 引用特定物件的例項方法
// 下面程式碼使用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;
  1. 引用構造器
@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));
   }
}