激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java8 新特性Lambda表達式實例詳解

Java8 新特性Lambda表達式實例詳解

2020-08-24 10:49Java之家 Java教程

這篇文章主要介紹了Java8 新特性Lambda表達式實例詳解的相關資料,需要的朋友可以參考下

Java8 新特性Lambda表達式實例詳解

在介紹Lambda表達式之前,我們先來看只有單個方法的Interface(通常我們稱之為回調接口):

?
1
2
3
public interface OnClickListener {
  void onClick(View v);
}

我們是這樣使用它的:

?
1
2
3
4
5
6
button.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    v.setText("lalala");
  }
});

這種回調模式在各種框架中非常流行,但是像上面這樣的匿名內部類并不是一個好的選擇,因為:

  • 語法冗余;
  • 匿名內部類中的this指針和變量容易產生誤解;
  • 無法捕獲非final局部變量;
  • 非靜態內部類默認持有外部類的引用,部分情況下會導致外部類無法被GC回收,導致內存泄露。

令人高興的是Java8為我們帶來了Lambda,下面我們看看利用Lambda如何實現上面的功能:

?
1
button.setOnClickListener(v -> v.setText("lalala"));

怎么樣?!五行代碼用一行就搞定了!!!

在這里補充個概念函數式接口;前面提到的OnClickListener接口只有一個方法,Java中大多數回調接口都有這個特征:比如Runnable和Comparator;我們把這些只擁有一個方法的接口稱之為函數式接口。

一、Lambda表達式

匿名內部類最大的問題在于其冗余的語法,比如前面的OnClickListener中五行代碼僅有一行是在執行任務。Lambda表達式是匿名方法,前面我們也看到了它用極其輕量的語法解決了這一問題。

下面給大家看幾個Lambda表達式的例子:

?
1
2
3
4
(int x, int y) -> x + y           //接收x和y兩個整形參數并返回他們的和
() -> 66                   //不接收任何參數直接返回66
(String name) -> {System.out.println(name);} //接收一個字符串然后打印出來
(View view) -> {view.setText("lalala");}   //接收一個View對象并調用setText方法

Lambda表達式語法由參數列表、->和函數體組成。函數體既可以是一個表達式也可以是一個代碼塊。

表達式:表達式會被執行然后返回結果。它簡化掉了return關鍵字。

代碼塊:顧名思義就是一坨代碼,和普通方法中的語句一樣。

二、目標類型

通過前面的例子我們可以看到,lambda表達式沒有名字,那我們怎么知道它的類型呢?答案是通過上下文推導而來的。例如,下面的表達式的類型是OnClickListener

?
1
OnClickListener listener = (View v) -> {v.setText("lalala");};

這就意味著同樣的lambda表達式在不同的上下文里有不同的類型

?
1
2
Runnable runnable = () -> doSomething(); //這個表達式是Runnable類型的
Callback callback = () -> doSomething(); //這個表達式是Callback類型的

編譯器利用lambda表達式所在的上下文所期待的類型來推導表達式的類型,這個被期待的類型被稱為目標類型。lambda表達式只能出現在目標類型為函數式接口的上下文中。

Lambda表達式的類型和目標類型的方法簽名必須一致,編譯器會對此做檢查,一個lambda表達式要想賦值給目標類型T則必須滿足下面所有的條件:

  1. T是一個函數式接口
  2. lambda表達式的參數必須和T的方法參數在數量、類型和順序上一致(一一對應)
  3. lambda表達式的返回值必須和T的方法的返回值一致或者是它的子類
  4. lambda表達式拋出的異常和T的方法的異常一致或者是它的子類

由于目標類型是知道lambda表達式的參數類型,所以我們沒必要把已知的類型重復一遍。也就是說lambda表達式的參數類型可以從目標類型獲取:

?
1
2
3
4
//編譯器可以推導出s1和s2是String類型
Comparator<String> c = (s1, s2) -> s1.compareTo(s2);
//當表達式的參數只有一個時括號也是可以省略的
button.setOnClickListener(v -> v.setText("lalala"));

ps: Java7中的泛型方法和<>構造器也是通過目標類型來進行類型推導的,如:

?
1
2
List<Integer> intList = Collections.emptyList>();
List<String> strList = new ArrayList<>();

三、作用域

在內部類中使用變量名和this非常容易出錯。內部類通過繼承得到的成員變量(包括來說object的)可能會把外部類的成員變量覆蓋掉,未做限制的this引用會指向內部類自己而非外部類。

而lambda表達式的語義就十分簡單:它不會從父類中繼承任何變量,也不用引入新的作用域。lambda表達式的參數及函數體里面的變量和它外部環境的變量具有相同的語義(this關鍵字也是一樣)。

下面我們舉個栗子吧!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HelloLambda {
 
  Runnable r1 = () -> System.out.println(this);
  Runnable r2 = () -> System.out.println(toString());
 
  @Override
  public String toString() {
    return "Hello, lambda!";
  }
 
  public static void main(String[] args) {
    new HelloLambda().r1.run();
    new HelloLambda().r2.run();
  }
}

上面的代碼最終會打印兩個Hello, lambda!,與之相類似的內部類則會打印出類似HelloLambda$1@32a890和HelloLambda$1@6b32098這種出乎意料的字符串。

總結:基于詞法作用域的理念,lambda表達式不可以掩蓋任何其所在上下文的局部變量。

四、變量捕獲

在Java7中,編譯器對內部類中引用的外部變量(即捕獲的變量)要求非常嚴格:如果捕獲的變量沒有被聲明為final就會產生一個編譯錯誤。但是在Java8中放寬了這一限制–對于lambda表達式和內部類,允許在其中捕獲那些符合有效只讀的局部變量(如果一個局部變量在初始化后從未被修改過,那么它就是有效只讀)。

?
1
2
3
4
Runnable getRunnable(String name){
  String hello = "hello";
  return () -> System.out.println(hello+","+name);
}

對于this的引用以及通過this對未限定字段的引用和未限定方法的調用本質上都屬于使用final局部變量。包含此類引用的lambda表達式相當于捕獲了this實例。在其他情況下,lambda對象不會保留任何對this的應用。

這個特性對內存管理是極好的:要知道在java中一個非靜態內部類會默認持有外部類實例的強引用,這往往會造成內存泄露。而在lambda表達式中如果沒有捕獲外部類成員則不會保留對外部類實例的引用。

不過盡管Java8放寬了對捕獲變量的語法限制,但試圖修改捕獲變量的行為是被禁止的,比如下面這個例子就是非法的:

?
1
2
int sum = 0;
list.forEach(i -> {sum += i;});

為什么要禁止這種行為呢?因為這樣的lambda表達式很容易引起race condition

lambda表達式不支持修改捕獲變量的另外一個原因是我們可以使用更好的方式來實現同樣的效果:使用規約(condition)。java.util.stream包提供了各種規約操作,關于Java8中的Stream API我們放到下一章介紹。

五、方法引用

lambda表達式允許我們定義一個匿名方法,并以函數式接口的方式使用它。Java8能夠在已有的方法上實現同樣的特性。

方法引用和lambda表達式擁有相同的特性(他們都需要一個目標類型,并且需要被轉化為函數式接口的實例),不過我們不需要為方法引用提供方法體,我們可以直接通過方法名引用已有方法。

以下面的代碼為例,假設我們要按照userName排序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
class User{
 
  private String userName;
 
  public String getUserName() {
    return userName;
  }
  ...
}
 
List<User> users = new ArrayList<>();
Comparator<User> comparator = Comparator.comparing(u -> u.getUserName());
Collections.sort(users, comparator);

我們可以用方法引用替換上面的lambda表達式

?
1
Comparator<User> comparator = Comparator.comparing(User::getUserName);

這里的User::getUserName被看做是lambda表達式的簡寫形式。盡管方法引用不一定會把代碼變得更緊湊,但它擁有更明確的語義–如果我們想要調用的方法擁有一個名字,那么我們就可以通過方法名調用它。

方法引用有很多種,它們的語法如下:

  1. 靜態方法引用:ClassName::methodName
  2. 實例上的實例方法引用:instanceReference::methodName
  3. 超類上的實例方法引用:super::methodName
  4. 類型上的實例方法引用:ClassName::methodName
  5. 構造方法引用:Class::new
  6. 數組構造方法引用:TypeName[]::new

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

原文鏈接:http://blog.csdn.net/baron_leizhang/article/details/59528784

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 欧美一级免费视频 | 亚洲人成免费 | 91在线观看| 精国产品一区二区三区四季综 | 国产精品一区二区三区99 | 韩国精品一区二区三区四区五区 | 黄网站进入 | 99日韩精品视频 | 午夜精品久久久久久中宇 | 亚欧在线免费观看 | 久久久久成人精品免费播放 | 久草在线资源福利站 | 久久久www成人免费精品 | 国产日产精品久久久久快鸭 | 欧美高清在线精品一区二区不卡 | 国产刺激高潮av | 国产精品成人av片免费看最爱 | 91女上位 在线播放 bt 自拍 另类 综合 欧美 | 国产免费看| 99亚洲视频 | 高清国产午夜精品久久久久久 | 日本一区二区三区精品 | 久草在线观看资源 | 一级做a爱视频 | 羞羞的视频免费在线观看 | 久久丝袜脚交足黄网站免费 | 亚洲成在人 | 国产亚洲精品久久久久5区 99精品视频在线 | 日韩av电影免费看 | 一区二区三区视频播放 | 久久久一区二区三区视频 | 污黄视频在线观看 | 久久不雅视频 | 亚洲爱爱网站 | 国产精品久久久久久久久久 | 91美女啪啪 | 男女无套免费视频 | 99精品视频一区二区 | 国产羞羞视频在线观看免费应用 | 青草久久网 | 国产自在线 |