JAVA8新特性 ------- Lamda表達式的運用

第一、瞭解什麼是lambda 表達式

  “Lambda 表達式”(lambda expression)是一個匿名函數,Lambda表達式基於數學中的λ演算得名,直接對應於其中的lambda抽象(lambda abstraction),是一個匿名函數,即沒有函數名的函數。Lambda表達式可以表示閉包(注意和數學傳統意義上的不同)。

   說白了Lamda就是一個函數表達式(學過C++和js的應該都很明白了),我們就可以衍生到函數式編程了,也就是在java中把函數作爲了一等公民

第二、爲什麼要用Lamda表達式

1.它簡化了我們編碼過程

2.“lambda 表達式”是一段可以傳遞的代碼,因此它可以被執行一次或多次(什麼意思呢,就是lambda表達式只需要我們傳遞參數和實現過程,它會自動根據上下文來匹配計算結果值

第三、我們怎麼徹底的學習Lamda表達式

1.語法

Lambda語法詳解

我們在此抽象一下lambda表達式的一般語法:

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

從lambda表達式的一般語法可以看出來,還是挺符合上面給出的非精確版本的定義–“一段帶有輸入參數的可執行語句塊”。

上面的lambda表達式語法可以認爲是最全的版本,寫起來還是稍稍有些繁瑣。彆着急,下面陸續介紹一下lambda表達式的各種簡化版:

1. 參數類型省略–絕大多數情況,編譯器都可以從上下文環境中推斷出lambda表達式的參數類型。這樣lambda表達式就變成了:

(param1,param2, ..., paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

所以我們最開始的例子就變成了(省略了List的創建):

List<String> lowercaseNames = names.stream().map((name) -> {return name.toLowerCase();}).collect(Collectors.toList());

2. 當lambda表達式的參數個數只有一個,可以省略小括號。lambda表達式簡寫爲:

param1 -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

所以最開始的例子再次簡化爲:

List<String> lowercaseNames = names.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());

3. 當lambda表達式只包含一條語句時,可以省略大括號、return和語句結尾的分號。lambda表達式簡化爲:

param1 -> statment

所以最開始的例子再次簡化爲:

List<String> lowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());

4. 使用Method Reference(具體語法後面介紹)

//注意,這段代碼在Idea 13.0.2中顯示有錯誤,但是可以正常運行
List<String> lowercaseNames = names.stream().map(String::toLowerCase).collect(Collectors.toList());

第二、實例

Lambda表達式眼中的外部世界

我們前面所有的介紹,感覺上lambda表達式像一個閉關鎖國的傢伙,可以訪問給它傳遞的參數,也能自己內部定義變量。但是卻從來沒看到其訪問它外部的變量。是不是lambda表達式不能訪問其外部變量?我們可以這樣想:lambda表達式其實是快速創建SAM接口的語法糖,原先的SAM接口都可以訪問接口外部變量,lambda表達式肯定也是可以(不但可以,在java8中還做了一個小小的升級,後面會介紹)。

String[] array = {"a", "b", "c"};
for(Integer i : Lists.newArrayList(1,2,3)){
  Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println);
}

上面的這個例子中,map中的lambda表達式訪問外部變量Integer i。並且可以訪問外部變量是lambda表達式的一個重要特性,這樣我們可以看出來lambda表達式的三個重要組成部分:

  • 輸入參數
  • 可執行語句
  • 存放外部變量的空間

不過lambda表達式訪問外部變量有一個非常重要的限制:變量不可變(只是引用不可變,而不是真正的不可變)。

String[] array = {"a", "b", "c"};
for(int i = 1; i<4; i++){
  Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println);
}

上面的代碼,會報編譯錯誤。因爲變量i被lambda表達式引用,所以編譯器會隱式的把其當成final來處理(ps:大家可以想象問什麼上一個例子不報錯,而這個報錯。)細心的讀者肯定會發現不對啊,以前java的匿名內部類在訪問外部變量的時候,外部變量必須用final修飾。Bingo,在java8對這個限制做了優化(前面說的小小優化),可以不用顯示使用final修飾,但是編譯器隱式當成final來處理。

lambda眼中的this

在lambda中,this不是指向lambda表達式產生的那個SAM對象,而是聲明它的外部對象。

方法引用(Method reference)和構造器引用(construct reference)

方法引用

前面介紹lambda表達式簡化的時候,已經看過方法引用的身影了。方法引用可以在某些條件成立的情況下,更加簡化lambda表達式的聲明。方法引用語法格式有以下三種:

  • objectName::instanceMethod
  • ClassName::staticMethod
  • ClassName::instanceMethod

前兩種方式類似,等同於把lambda表達式的參數直接當成instanceMethod|staticMethod的參數來調用。比如System.out::println等同於x->System.out.println(x);Math::max等同於(x, y)->Math.max(x,y)。

最後一種方式,等同於把lambda表達式的第一個參數當成instanceMethod的目標對象,其他剩餘參數當成該方法的參數。比如String::toLowerCase等同於x->x.toLowerCase()。

構造器引用

構造器引用語法如下:ClassName::new,把lambda表達式的參數當成ClassName構造器的參數 。例如BigDecimal::new等同於x->new BigDecimal(x)。

吐槽一下方法引用

表面上看起來方法引用和構造器引用進一步簡化了lambda表達式的書寫,但是個人覺得這方面沒有Scala的下劃線語法更加通用。比較才能看出,翠花,上代碼!

List<String> names = new ArrayList<>();
names.add("TaoBao");
names.add("ZhiFuBao");
names.stream().map(name -> name.charAt(0)).collect(Collectors.toList());

上面的這段代碼就是給定一個String類型的List,獲取每個String的首字母,並將其組合成新的List。這段代碼就沒辦法使用方法引用來簡化。接下來,我們簡單對比一下Scala的下劃線語法(不必太糾結Scala的語法,這裏只是做個對比):

//省略List的初始化
List[String] names = ....
names.map(_.charAt(0))

在Scala中基本不用寫lambda表達式的參數聲明。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章