1. 程式人生 > >還看不懂同事的程式碼?Lambda 表示式、函式介面瞭解一下

還看不懂同事的程式碼?Lambda 表示式、函式介面瞭解一下

當前時間:2019年 11月 11日,距離 JDK 14 釋出時間(2020年3月17日)還有多少天?

// 距離JDK 14 釋出還有多少天?
LocalDate jdk14 = LocalDate.of(2020, 3, 17);
LocalDate nowDate = LocalDate.now();
System.out.println("距離JDK 14 釋出還有:"+nowDate.until(jdk14,ChronoUnit.DAYS)+"天");

1. 前言

Java 8 早已經在2014 年 3月 18日釋出,毫無疑問 Java 8 對 Java 來說絕對算得上是一次重大版本更新,它包含了十多項語言、庫、工具、JVM 等方面的新特性。比如提供了語言級的匿名函式,也就是被官方稱為 Lambda

的表示式語法(外界也稱為閉包,Lambda 的引入也讓流式操作成為可能,減少了程式碼編寫的複雜性),比如函式式介面,方法引用,重複註解。再比如 Optional 預防空指標,Stearm 流式操作,LocalDateTime 時間操作等。

在前面的文章裡已經介紹了 Java 8 的部分新特性。

  1. Jdk14 都要出了,Jdk8 的時間處理姿勢還不瞭解一下?

  2. Jdk14都要出了,還不能使用 Optional優雅的處理空指標?

這一次主要介紹一下 Lambda 的相關情況。

2. Lambda 介紹

Lambda 名字來源於希臘字母表中排序第十一位的字母 λ,大寫為Λ,英語名稱為 Lambda

。在 Java 中 Lambda 表示式(lambda expression)是一個匿名函式,在編寫 Java 中的 Lambda 的時候,你也會發現 Lambda 不僅沒有函式名稱,有時候甚至連入參和返回都可以省略,這也讓程式碼變得更加緊湊。

3. 函式介面介紹

上面說了這次是介紹 Lambda 表示式,為什麼要介紹函式介面呢?其實 Java 中的函式介面在使用時,可以隱式的轉換成 Lambda 表示式,在 Java 8中已經有很多介面已經宣告為函式介面,如 Runnable、Callable、Comparator 等。

函式介面的例子可以看下 Java 8 中的 Runnable 原始碼(去掉了註釋)。

package java.lang;

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

那麼什麼樣子的接口才是函式介面呢?有一個很簡單的定義,也就是隻有一個抽象函式的介面,函式介面使用註解 @FunctionalInterface 進行宣告(註解宣告不是必須的,如果沒有註解,也是隻有一個抽象函式,依舊會被認為是函式介面)。多一個或者少一個抽象函式都不能定義為函式介面,如果使用了函式介面註解又不止一個抽象函式,那麼編譯器會拒絕編譯。函式介面在使用時候可以隱式的轉換成 Lambda 表示式。

Java 8 中很多有很多不同功能的函式介面定義,都放在了 Java 8 新增的 java.util.function包內。下面是一些關於 Java 8 中函式介面功能的描述。

序號 介面 & 描述
BiConsumer 代表了一個接受兩個輸入引數的操作,並且不返回任何結果
BiFunction 代表了一個接受兩個輸入引數的方法,並且返回一個結果
BinaryOperator 代表了一個作用於於兩個同類型操作符的操作,並且返回了操作符同類型的結果
BiPredicate 代表了一個兩個引數的boolean值方法
BooleanSupplier 代表了boolean值結果的提供方
Consumer 代表了接受一個輸入引數並且無返回的操作
DoubleBinaryOperator 代表了作用於兩個double值操作符的操作,並且返回了一個double值的結果。
DoubleConsumer 代表一個接受double值引數的操作,並且不返回結果。
DoubleFunction 代表接受一個double值引數的方法,並且返回結果
DoublePredicate 代表一個擁有double值引數的boolean值方法
DoubleSupplier 代表一個double值結構的提供方
DoubleToIntFunction 接受一個double型別輸入,返回一個int型別結果。
DoubleToLongFunction 接受一個double型別輸入,返回一個long型別結果
DoubleUnaryOperator 接受一個引數同為型別double,返回值型別也為double 。
Function 接受一個輸入引數,返回一個結果。
IntBinaryOperator 接受兩個引數同為型別int,返回值型別也為int 。
IntConsumer 接受一個int型別的輸入引數,無返回值 。
IntFunction 接受一個int型別輸入引數,返回一個結果 。
IntPredicate 接受一個int輸入引數,返回一個布林值的結果。
IntSupplier 無引數,返回一個int型別結果。
IntToDoubleFunction 接受一個int型別輸入,返回一個double型別結果 。
IntToLongFunction 接受一個int型別輸入,返回一個long型別結果。
IntUnaryOperator 接受一個引數同為型別int,返回值型別也為int 。
LongBinaryOperator 接受兩個引數同為型別long,返回值型別也為long。
LongConsumer 接受一個long型別的輸入引數,無返回值。
LongFunction 接受一個long型別輸入引數,返回一個結果。
LongPredicate 接受一個long輸入引數,返回一個布林值型別結果。
LongSupplier 無引數,返回一個結果long型別的值。
LongToDoubleFunction 接受一個long型別輸入,返回一個double型別結果。
LongToIntFunction 接受一個long型別輸入,返回一個int型別結果。
LongUnaryOperator 接受一個引數同為型別long,返回值型別也為long。
ObjDoubleConsumer 接受一個object型別和一個double型別的輸入引數,無返回值。
ObjIntConsumer 接受一個object型別和一個int型別的輸入引數,無返回值。
ObjLongConsumer 接受一個object型別和一個long型別的輸入引數,無返回值。
Predicate 接受一個輸入引數,返回一個布林值結果。
Supplier 無引數,返回一個結果。
ToDoubleBiFunction 接受兩個輸入引數,返回一個double型別結果
ToDoubleFunction 接受一個輸入引數,返回一個double型別結果
ToIntBiFunction 接受兩個輸入引數,返回一個int型別結果。
ToIntFunction 接受一個輸入引數,返回一個int型別結果。
ToLongBiFunction 接受兩個輸入引數,返回一個long型別結果。
ToLongFunction 接受一個輸入引數,返回一個long型別結果。
UnaryOperator 接受一個引數為型別T,返回值型別也為T。

(上面表格來源於菜鳥教程)

3. Lambda 語法

Lambda 的語法主要是下面幾種。

  1. (params) -> expression

  2. (params) -> {statements;}

Lambda 的語法特性。

  1. 使用 -> 分割 Lambda 引數和處理語句。
  2. 型別可選,可以不指定引數型別,編譯器可以自動判斷。
  3. 圓括號可選,如果只有一個引數,可以不需要圓括號,多個引數必須要圓括號。
  4. 花括號可選,一個語句可以不用花括號,多個引數則花括號必須。
  5. 返回值可選,如果只有一個表示式,可以自動返回,不需要 return 語句;花括號中需要 return 語法。
    1. Lambda 中引用的外部變數必須為 final 型別,內部宣告的變數不可修改,內部宣告的變數名稱不能與外部變數名相同。

舉幾個具體的例子, params 在只有一個引數或者沒有引數的時候,可以直接省略不寫,像這樣。

// 1.不需要引數,沒有返回值,輸出 hello
()->System.out.pritnln("hello");

// 2.不需要引數,返回 hello
()->"hello";

// 3. 接受2個引數(數字),返回兩數之和 
(x, y) -> x + y  
  
// 4. 接受2個數字引數,返回兩數之和 
(int x, int y) -> x + y  
  
// 5. 兩個數字引數,如果都大於10,返回和,如果都小於10,返回差
(int x,int y) ->{
  if( x > 10 && y > 10){
    return x + y;
  }
  if( x < 10 && y < 10){
    return Math.abs(x-y);
  }
};

通過上面的幾種情況,已經可以大致瞭解 Lambda 的語法結構了。

4. Lambda 使用

4.1 對於函式介面

從上面的介紹中已經知道了 Runnable 介面已經是函式介面了,它可以隱式的轉換為 Lambda 表示式進行使用,通過下面的建立執行緒並執行的例子看下 Java 8 中 Lambda 表示式的具體使用方式。

/**
 * Lambda 的使用,使用 Runnable 例子
 * @throws InterruptedException
 */
@Test
public void createLambda() throws InterruptedException {
    // 使用 Lambda 之前
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("JDK8 之前的執行緒建立");
        }
    };
   new Thread(runnable).start();
   // 使用 Lambda 之後
   Runnable runnable1Jdk8 = () -> System.out.println("JDK8 之後的執行緒建立");
   new Thread(runnable1Jdk8).start();
   // 更加緊湊的方式
   new Thread(() -> System.out.println("JDK8 之後的執行緒建立")).start();
}

可以發現 Java 8 中的 Lambda 碰到了函式介面 Runnable,自動推斷了要執行的 run 方法,不僅省去了 run 方法的編寫,也程式碼變得更加緊湊。

執行得到結果如下。

JDK8 之前的執行緒建立
JDK8 之後的執行緒建立
JDK8 之後的執行緒建立

上面的 Runnable 函式接口裡的 run 方法是沒有引數的情況,如果是有引數的,那麼怎麼使用呢?我們編寫一個函式介面,寫一個 say 方法接受兩個引數。

/**
 * 定義函式介面
 */
@FunctionalInterface
public interface FunctionInterfaceDemo {
    void say(String name, int age);
} 

編寫一個測試類。

 /**
  * 函式介面,Lambda 測試
  */
 @Test
 public void functionLambdaTest() {
     FunctionInterfaceDemo demo = (name, age) -> System.out.println("我叫" + name + ",我今年" + age + "歲了");
     demo.say("金庸", 99);
 }

輸出結果。

我叫金庸,我今年99歲了。

4.2 對於方法引用

方法引用這個概念前面還沒有介紹過,方法引用可以讓我們直接訪問類的例項或者方法,在 Lambda 只是執行一個方法的時候,就可以不用 Lambda 的編寫方式,而用方法引用的方式:例項/類::方法。這樣不僅程式碼更加的緊湊,而且可以增加程式碼的可讀性。

通過一個例子檢視方法引用。

@Getter
@Setter
@ToString
@AllArgsConstructor
static class User {
    private String name;
    private Integer age;
}
public static List<User> userList = new ArrayList<User>();
static {
    userList.add(new User("A", 26));
    userList.add(new User("B", 18));
    userList.add(new User("C", 23));
    userList.add(new User("D", 19));
}
/**
 * 測試方法引用
 */
@Test
public void methodRef() {
    User[] userArr = new User[userList.size()];
    userList.toArray(userArr);
    // User::getAge 呼叫 getAge 方法
    Arrays.sort(userArr, Comparator.comparing(User::getAge));
    for (User user : userArr) {
        System.out.println(user);
    }
}

得到輸出結果。

Jdk8Lambda.User(name=B, age=18)
Jdk8Lambda.User(name=D, age=19)
Jdk8Lambda.User(name=C, age=23)
Jdk8Lambda.User(name=A, age=26)

4.3 對於遍歷方式

Lambda 帶來了新的遍歷方式,Java 8 為集合增加了 foreach 方法,它可以接受函式介面進行操作。下面看一下 Lambda 的集合遍歷方式。

/**
 * 新的遍歷方式
 */
@Test
public void foreachTest() {
    List<String> skills = Arrays.asList("java", "golang", "c++", "c", "python");
    // 使用 Lambda 之前
    for (String skill : skills) {
        System.out.print(skill+",");
    }
    System.out.println();
    // 使用 Lambda 之後
    // 方式1,forEach+lambda
    skills.forEach((skill) -> System.out.print(skill+","));
    System.out.println();
    // 方式2,forEach+方法引用
    skills.forEach(System.out::print);
}

執行得到輸出。

java,golang,c++,c,python,
java,golang,c++,c,python,
javagolangc++cpython

4.4 對於流式操作

得益於 Lambda 的引入,讓 Java 8 中的流式操作成為可能,Java 8 提供了 stream 類用於獲取資料流,它專注對資料集合進行各種高效便利操作,提高了程式設計效率,且同時支援序列和並行的兩種模式匯聚計算。能充分的利用多核優勢。

流式操作如此強大, Lambda 在流式操作中怎麼使用呢?下面來感受流操作帶來的方便與高效。

流式操作一切從這裡開始。

// 為集合建立序列流
stream()
// 為集合建立並行流
parallelStream()

流式操作的去重 distinct和過濾 filter

@Test
public void streamTest() {
    List<String> skills = Arrays.asList("java", "golang", "c++", "c", "python", "java");
    // Jdk8 之前
    for (String skill : skills) {
        System.out.print(skill + ",");
    }
    System.out.println();
    // Jdk8 之後-去重遍歷
    skills.stream().distinct().forEach(skill -> System.out.print(skill + ","));
    System.out.println();
    // Jdk8 之後-去重遍歷
    skills.stream().distinct().forEach(System.out::print);
    System.out.println();
    // Jdk8 之後-去重,過濾掉 ptyhon 再遍歷
    skills.stream().distinct().filter(skill -> skill != "python").forEach(skill -> System.out.print(skill + ","));
    System.out.println();
    // Jdk8 之後轉字串
    String skillString = String.join(",", skills);
    System.out.println(skillString);
}

執行得到結果。

java,golang,c++,c,python,java,
java,golang,c++,c,python,
javagolangc++cpython
java,golang,c++,c,
java,golang,c++,c,python,java

流式操作的資料轉換(也稱對映)map

 /**
  * 資料轉換
  */
 @Test
 public void mapTest() {
     List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5);
     // 資料轉換
     numList.stream().map(num -> num * num).forEach(num -> System.out.print(num + ","));

     System.out.println();

     // 資料收集
     Set<Integer> numSet = numList.stream().map(num -> num * num).collect(Collectors.toSet());
     numSet.forEach(num -> System.out.print(num + ","));
 }

執行得到結果。

1,4,9,16,25,
16,1,4,9,25,

流式操作的數學計算。

/**
 * 數學計算測試
 */
@Test
public void mapMathTest() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
    System.out.println("最小值:" + stats.getMin());
    System.out.println("最大值:" + stats.getMax());
    System.out.println("個數:" + stats.getCount());
    System.out.println("和:" + stats.getSum());
    System.out.println("平均數:" + stats.getAverage());
    // 求和的另一種方式
    Integer integer = list.stream().reduce((sum, cost) -> sum + cost).get();
    System.out.println(integer);
}

執行得到結果。

得到輸出
最小值:1
最大值:5
個數:5
和:15
平均數:3.0
15

5. Lambda 總結

Lamdba 結合函式介面,方法引用,型別推導以及流式操作,可以讓程式碼變得更加簡潔緊湊,也可以藉此開發出更加強大且支援平行計算的程式,函式程式設計也為 Java 帶來了新的程式設計方式。但是缺點也很明顯,在實際的使用過程中可能會發現調式困難,測試表示 Lamdba 的遍歷效能並不如 for 的效能高,同事可能沒有學習導致看不懂 Lamdba 等(可以推薦來看這篇文章)。

文章程式碼已經上傳到 https://github.com/niumoo/jdk-feature) 。

<完>

個人網站:https://www.codingme.net
如果你喜歡這篇文章,可以關注公眾號,一起成長。
關注公眾號回覆資源可以沒有套路的獲取全網最火的的 Java 核心知識整理&面試資料。

相關推薦

同事程式碼Lambda 表示式函式介面瞭解一下

當前時間:2019年 11月 11日,距離 JDK 14 釋出時間(2020年3月17日)還有多少天? // 距離JDK 14 釋出還有多少天? LocalDate jdk14 = LocalDate.of(2020, 3, 17); LocalDate nowDate = LocalDate.now();

同事程式碼?快來補一波 Java 7 語法特性

前言 Java 平臺自出現到目前為止,已經 20 多個年頭了,這 20 多年間 Java 也一直作為最流行的程式設計語言之一,不斷面臨著其他新興程式語言的挑戰與衝擊。Java 語言是一種靜態強型別語言,這樣的語言特性可以讓 Java 編譯器在編譯階段發現錯誤,這對於構建出一個穩定安全且健壯的應用來說,尤為重要

同事程式碼?超強的 Stream 流操作姿勢學習一下

Java 8 新特性系列文章索引。 Jdk14都要出了,還不能使用 Optional優雅的處理空指標? Jdk14 都要出了,Jdk8 的時間處理姿勢還不瞭解一下? 還看不懂同事的程式碼?Lambda 表示式、函式介面瞭解一下 前言 我們都知道 Lambda 和 Stream 是 Java 8 的兩大亮點

curl -l的資訊詳解(這樣,就沒天理了)

[[email protected] ~]# curl -I "http://www.baidu.com" HTTP/1.1 200 OK Accept-Ranges: bytes Cache-Control: private, no-cache, no-store, proxy-reva

不要寫很酷但同事的Java程式碼

你好呀,我是沉默王二,一個和黃家駒一樣身高,和劉德華一樣顏值的程式設計師。為了提高 Java 程式設計的技藝,我最近在 GitHub 上學習一些高手編寫的程式碼。下面這一行程式碼(出自大牛之手)據說可以征服你的朋友,讓他們覺得你寫的程式碼很 6,來欣賞一下吧。 IntStream.range(1, 

程式設計師晒出實習生的一段程式碼,表示!網友:out了,新寫法

技術總是在不停的更新迭代,這就是程式設計師為什麼要保持學習能力的原因了,與其他行業相比,程式設計師不學習新知識,只是保持現有的技術,幾年後再回頭看看,就可能與同行落下去一大截,當然,對於技術的更新也是徐徐漸進的,只要有一定的技術嗅覺,多留意一些技術官方網站的最新發布, 多留意一下發展的動向就不會那麼

程式設計師就業三年,竟這短短一行程式碼!網友的解釋卻讓我笑翻

現在的程式碼邏輯,似乎越來越讓人難以捉摸了,各種奇葩的程式碼層出不窮。有些程式碼一眼看上去似乎有錯誤,可是一理程式碼邏輯,好像又沒有錯誤的樣子,著實消遣了很多程式設計師! 在這我還是要推薦下我自己的前端學習群:733581373,不管你是小白還是大牛,小編我都挺歡迎,不定

RISC-V雙週簡報0x04:光有Chisel不夠,又來一個SpinalHDL,Scala我真心啊!(2017-08-03)

RISC-V 雙週簡報 (2017-08-03) RV新聞 RISC-V教育專題郵件列表成立 RISC-V版Compiler Explorer Michael Clark,rv8二進位制解釋執行器的作者,將RISC-V的GNU GCC7.1編譯器加入了Com

程式設計師:為什麼幾個月後我自己寫的程式碼了?

寫在開始的"註釋很重要","註釋很重要","註釋很重要",重要的事情說三遍。 程式設計師們大多都會有過這樣的經歷,就是要看別人寫的程式碼。比如說公司有同事離職了,他的業務就需要有其它的小夥伴們給接下來繼續維護。這對好多程式設計師來說是一種折磨,邊看邊罵,這寫的是什麼東西呀。相信好多人都有這種經歷

load傳遞引數的三種方式(步驟詳細附程式碼,要是你們我就放棄程式設計)

方式一:直接獲取上個頁面的資料; 這種方式主要依賴load這個方法的原理,這個其實就是一個本地ajax請求,所以前後兩個頁面是互通的,其資料是可以直接拿到的。例項如下: a頁面的程式碼: <script> var adata = "12"; $("#d

#Java程式設計師面試碰到一段程式碼:線上等解答!網友:,下一題

作為一名程式設計師,想要有高深的技術,那麼良好的邏輯思維能力是不可或缺的!很多企業在面試程式設計師的時候,都會出一些面試題來測試面試者,看看他們技術和邏輯能力能不能達到入職的標準,其實這些題不算太難,只要有一些相關的經驗,也是可以答到關鍵點上! 如果有想學習java的程式設計師,可來我們的jav

毒瘤header(程式碼裡的巨集和函式可以來這裡找)

實際上大多都是從別人程式碼裡偷來的 #include <bits/stdc++.h> using namespace std; typedef double lf; typedef long long ll; typedef long double llf; typedef vector<

是程式設計師的搞笑段子

1、 你好呀,你想聽一個TCP笑話嗎?   我很樂意聽一個TCP笑話。 好的,那我來和你說一個TCP笑話。 好的,那我將會聽到一個TCP笑話。 你準備好聽一個TCP笑話了嗎? 2、 如何區分HTML和HTML5? 用IE開啟,打不開嗎?那就是HTML5沒錯了。

網站中友情連結的部分程式碼,表示

package com.daowen.action;public class FriendlinkAction extends PageActionBase {public void onLoad() {}/**********************************

週五份的程式設計師段子,是程式設計師你,讓你笑破肚皮!

1.昨天加班寫的程式碼出了問題,深夜接到電話,正在電腦前修改的你…… 2.當有人讓我幫他除錯程式碼時。 3.修bug,一定要再檢查一遍…… 相信這裡有很多學習java的朋友,小編整理了一份java方面的學習資料, 有想要學習java的可以加一下我的學習群的

論文都,你搞什麼人工智慧?

轉載自https://blog.csdn.net/kwame211/article/details/78109304本次 Chat 的第一部分:首先講解如何從零基礎開始閱讀一篇機器學習方向的論文,以及對待論文中的數學問題。隨後,從一篇經典論文入手,講解如何快速梳理和理解一個深

最短路徑A*演算法原理及java程式碼實現(是我的失敗)

package astar; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; imp

在為英文技術文件苦惱嗎,或許這款神器能幫你徹底解決它!(我是免費真福利)

本文首發於:微信公眾號「運維之美」,公眾號 ID:Hi-Linux。 「運維之美」是一個有情懷、有態度,專注於 Linux 運維相關技術文章分享的公眾號。公眾號致力於為廣大運維工作者分享各類技術文章和釋出最前沿的科技資訊。公眾號的核心理念是:分享,我們認為只有分享才能使我們的團體更強大。如果你想第一時間

程式碼會用框架,新手程式設計師入職後如何快速上手專案?

大家好,我是良許。 對於職場新人,特別是應屆畢業生,他們拿到offer之後,進入公司後會有一段時間的焦慮感。比如說,不懂公司專案開發流程,程式碼看不懂,業務流程也不知道,框架不會用,等等還有各種各樣的問題。 所以很多人一開始都會在擔心自己能不能勝任這個職位,會不會連試用期都過不了。其實這個心態是很常見的,

我用 Java 8 寫了一段邏輯,同事直呼,你試試看。。

## 業務背景 首先,業務需求是這樣的,從第三方電商平臺拉取所有訂單,然後儲存到公司自己的資料庫,需要判斷是否有物流資訊,如果有物流資訊,還需要再進行上傳。 而第三方介面返回的資料是 `JSON` 格式的,其中物流資訊卻藏的十分深,如下面所示,JSON 節點是這樣的: > xxxOrder >