首頁  >  文章  >  Java  >  java中lambda表達式語法說明

java中lambda表達式語法說明

高洛峰
高洛峰原創
2017-01-23 15:42:101756瀏覽

語法說明

一個lambda表達式由以下幾個部分組成:

1. 在圓括號中以逗號分隔的形參列表。在CheckPerson.test方法中包含一個參數p,代表了一個Person類別的實例。注意:lambda表達式中的參數的型別是可以省略的;此外,如果只有一個參數的話連括號也是可以省略的。例如上一節曾提到的代碼:

p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
    && p.getAge() <= 25

2. 箭頭符號:->。用來分隔參數和函數體。

3. 函數體。由一個表達式或程式碼區塊組成。在上一節的例子中使用了這樣的表達式:

   
p.getGender() == Person.Sex.MALE
      && p.getAge() >= 18
      && p.getAge() <= 25

如果使用的是表達式,java運行時會計算並傳回表達式的值。另外,也可以選擇在程式碼區塊中使用return語句:

p -> {
  return p.getGender() == Person.Sex.MALE
      && p.getAge() >= 18
      && p.getAge() <= 25;
}

不過return語句並不是表達式。在lambda表達式中需要將語句用花括號括起來,然而卻沒有必要在只是調用一個返回值為空的方法時也用花括號括起來,所以如下的寫法也是正確的:

email -> System.out.println(email)

lambda表達式和方法的聲明看起來有很多類似的地方。所以也可以把lambda表達式視為匿名方法,也就是沒有定義名字的方法。

以上提到的lambda表達式都是只使用了一個參數作為形參的表達式。下面的實例類,Caulator,示範如何使用多個參數作為形參:

package com.zhyea.zytools;
 
public class Calculator {
 
  interface IntegerMath {
    int operation(int a, int b);
  }
 
  public int operateBinary(int a, int b, IntegerMath op) {
    return op.operation(a, b);
  }
 
  public static void main(String... args) {
    Calculator myApp = new Calculator();
    IntegerMath addition = (a, b) -> a + b;
    IntegerMath subtraction = (a, b) -> a - b;
    System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition));
    System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction));
  }
}

程式碼中operateBinary方法使用了兩個整數參數來執行算數運算。這裡的算數操作本身就是IntegerMath介面的一個實例。在上面的程式中使用lambda表達式定義了兩個算數運算:addition和subtraction。執行程式會列印以下內容:

40 + 2 = 42
20 - 10 = 10

存取外部類別的局部變數 

類似於局部類別或匿名類別,lambda表達式也可以存取外部類別的局部變數。不同的是,使用lambda表達式時無需考慮覆蓋之類的問題。 lambda表達式只是一個詞法上的概念,這意味著它不需要從超類別中繼承任何名稱,也不會引入新的作用域。也就是說,在lambda表達式中的宣告和在它的外在環境中的宣告意義是一樣的。在下面的範例中對此作了示範:

package com.zhyea.zytools;
 
import java.util.function.Consumer;
 
public class LambdaScopeTest {
 
  public int x = 0;
 
  class FirstLevel {
 
    public int x = 1;
 
    void methodInFirstLevel(int x) {
      //如下的语句会导致编译器在statement A处报错“local variables referenced from a lambda expression must be final or effectively final”
      // x = 99;
      Consumer<integer> myConsumer = (y) ->{
        System.out.println("x = " + x); // Statement A
        System.out.println("y = " + y);
        System.out.println("this.x = " + this.x);
        System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x);
      };
 
      myConsumer.accept(x);
    }
  }
 
  public static void main(String... args) {
    LambdaScopeTest st = new LambdaScopeTest();
    LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
    fl.methodInFirstLevel(23);
  }
}

   


這段程式碼會輸出如下內容:

   
x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0


如果使用範例中的表達式別中的表達式參數中所替換式的參數。器就會報錯:

Consumer<integer> myConsumer = (x) ->{
      // ....
    };


編譯器報錯訊息是:“variable x is already defined in method methodInFirstLevel(int)”,就是說在方法methodInFirstLevel中已經定義了變數變數。報錯是因為lambda表達式不會引入新的作用域。也因此呢,可以在lambda表達式中直接存取外部類別的域字段、方法以及形參。在這個例子中,lambda表達式myConsumer直接訪問了方法methodInFirstLevel的形參x。而存取外部類別的成員時也是直接使用this關鍵字。在這個例子中this.x指的就是FirstLevel.x。

然而,和局部類別或匿名類別一樣,lambda表達式也只能存取局部變數或外部被宣告為final(或等同於final)的成員。例如,我們將範例程式碼methodInFirstLevel方法中「x=99」前面的註解去掉:

//如下的语句会导致编译器在statement A处报错“local variables referenced from a lambda expression must be final or effectively final”
x = 99;
Consumer<integer> myConsumer = (y) ->{
  System.out.println("x = " + x); // Statement A
  System.out.println("y = " + y);
  System.out.println("this.x = " + this.x);
  System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x);
};


因為在這段語句中修改了參數x的值,使得methodInFirstLevel的參數x不可以再被視為final式的。因此java編譯器就會在lambda表達式存取局部變數x的地方報出類似「local variables referenced from a lambda expression must be final or effectively final」這樣的錯誤。

目標型別

該如何判斷lambda表達式的型別呢。再來看篩選適齡服兵役人員的程式碼:

   
p -> p.getGender() == Person.Sex.MALE
       && p.getAge() >= 18
       && p.getAge() <= 25


這段程式碼在兩處用過:

public static void printPersons(List roster, CheckPerson printPersonsWithPredicate(List roster, Predicate tester) —— 方案六 

在呼叫printPersons方法時,這個方法期望一個CheckPerson 類型的參數,此時上面的表達式就是一個CheckPerson 類型的表達式。在呼叫printPersonsWithPredicate方法時,期望一個Predicate類型的參數,此時同樣的表達式就是Predicate類型的。像這樣子的,由方法期望的類型來決定的類型就叫做目標類型(其實我覺得scala中的類型推論用在這裡比較適合)。 java編譯器就是透過目標類型的上下文情境或發現lambda表達式時的位置來判斷lambda表達式的類型的。這也就意味著只能在Java編譯器可以推斷出類型的位置使用Lambda表達式:

變量聲明;

賦值;

返回語句;

數組初始化;

方法或者構造器參數;

lambda表達式方法體;

條件表達式(?:);

拋出異常時。 


目標型別與方法參數


对于方法参数,Java编译器还需要依赖两个语言特性来决定目标类型:重载解析和类型参数推断。

看一下下面的这两个函数式接口( java.lang.Runnable and java.util.concurrent.Callabled94943c0b4933ad8cac500132f64757f):

public interface Runnable {
    void run();
  }
 
  public interface Callable<v> {
    V call();
  }


Runnable.run()方法没有返回值,而Callable.call()方法有。

假设我们像下面这样重载了invoke方法:

void invoke(Runnable r) {
   r.run();
 }
 
 <t> T invoke(Callable<t> c) {
   return c.call();
 }


那么在下面的语句中将会调用哪个方法呢:

String s = invoke(() -> "done");

调用的是invoke(Callable8742468051c85b06f0a0af9e3e506b5c),因为这个方法有返回值,而invoke(Runnable8742468051c85b06f0a0af9e3e506b5c)没有返回值。在这种情况下lambda表达式(() -> “done”)的类型是Callable8742468051c85b06f0a0af9e3e506b5c。

序列化

如果一个lambda表达式的目标类型还有它调用的参数的类型都是可序列化的,那么lambda表达式也是可序列化的。然而就像内部类一样,强烈不建议对lambda表达式进行序列化。

更多java中lambda表达式语法说明相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn