帶你解惑大廠必會使用的 Lambda表達式、函數式接口

前言:

應廣大讀者的須要,霈哥給你們帶來新一期的乾貨啦!java

帶你解惑大廠必會使用的 Lambda表達式、函數式接口

[帶你解惑大廠必會使用的 Stream流、方法引用]

若對你和身邊的朋友有幫助, 抓緊關注 IT霈哥 點贊! 點贊! 點贊! 評論!收藏! 分享給更多的朋友共同窗習交流, 天天持續更新離不開你的支持!

第一章 Lambda表達式

1.1 函數式編程思想概述

在數學中,函數就是有輸入量、輸出量的一套計算方案,也就是「拿什麼東西作什麼事情」。相對而言,面向對象過度強調「必須經過對象的形式來作事情」,而函數式思想則儘可能忽略面向對象的複雜語法——強調作什麼,而不是以什麼形式作編程

作什麼,而不是怎麼作後端

咱們真的但願建立一個匿名內部類對象嗎?不。咱們只是爲了作這件事情而不得不建立一個對象。咱們真正但願作的事情是:將run方法體內的代碼傳遞給Thread類知曉。數組

傳遞一段代碼——這纔是咱們真正的目的。而建立對象只是受限於面向對象語法而不得不採起的一種手段方式。那,有沒有更加簡單的辦法?若是咱們將關注點從「怎麼作」迴歸到「作什麼」的本質上,就會發現只要可以更好地達到目的,過程與形式其實並不重要。markdown

1.2 Lambda的優化

當須要啓動一個線程去完成任務時,一般會經過java.lang.Runnable接口來定義任務內容,並使用java.lang.Thread類來啓動該線程。多線程

傳統寫法,代碼以下:app

public class Demo03Thread {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("多線程任務執行!");
			}
		}).start();
	}
}
複製代碼

本着「一切皆對象」的思想,這種作法是無可厚非的:首先建立一個Runnable接口的匿名內部類對象來指定任務內容,再將其交給一個線程來啓動。分佈式

代碼分析:ide

對於Runnable的匿名內部類用法,能夠分析出幾點內容:函數式編程

  • Thread類須要Runnable接口做爲參數,其中的抽象run方法是用來指定線程任務內容的核心;
  • 爲了指定run的方法體,不得不須要Runnable接口的實現類;
  • 爲了省去定義一個RunnableImpl實現類的麻煩,不得不使用匿名內部類;
  • 必須覆蓋重寫抽象run方法,因此方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;
  • 而實際上,彷佛只有方法體纔是關鍵所在

Lambda表達式寫法,代碼以下:

藉助Java 8的全新語法,上述Runnable接口的匿名內部類寫法能夠經過更簡單的Lambda表達式達到等效:

public class Demo04LambdaRunnable {
	public static void main(String[] args) {
		new Thread(() -> System.out.println("多線程任務執行!")).start(); // 啓動線程
	}
}
複製代碼

這段代碼和剛纔的執行效果是徹底同樣的,能夠在1.8或更高的編譯級別下經過。從代碼的語義中能夠看出:咱們啓動了一個線程,而線程任務的內容以一種更加簡潔的形式被指定。

再也不有「不得不建立接口對象」的束縛,再也不有「抽象方法覆蓋重寫」的負擔,就是這麼簡單!

1.3 Lambda的格式

標準格式:

Lambda省去面向對象的條條框框,格式由3個部分組成:

  • 一些參數
  • 一個箭頭
  • 一段代碼

Lambda表達式的標準格式爲:

(參數類型 參數名稱) -> { 代碼語句 }
複製代碼

格式說明:

  • 小括號內的語法與傳統方法參數列表一致:無參數則留空;多個參數則用逗號分隔。
  • ->是新引入的語法格式,表明指向動做。
  • 大括號內的語法與傳統方法體要求基本一致。

匿名內部類與lambda對比:

new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("多線程任務執行!");
			}
}).start();
複製代碼

仔細分析該代碼中,Runnable接口只有一個run方法的定義:

  • public abstract void run();

即制定了一種作事情的方案(其實就是一個方法):

  • 無參數:不須要任何條件便可執行該方案。
  • 無返回值:該方案不產生任何結果。
  • 代碼塊(方法體):該方案的具體執行步驟。

一樣的語義體如今Lambda語法中,要更加簡單:

() -> System.out.println("多線程任務執行!")
複製代碼
  • 前面的一對小括號即run方法的參數(無),表明不須要任何條件;
  • 中間的一個箭頭表明將前面的參數傳遞給後面的代碼;
  • 後面的輸出語句即業務邏輯代碼。

參數和返回值:

下面舉例演示java.util.Comparator<T>接口的使用場景代碼,其中的抽象方法定義爲:

  • public abstract int compare(T o1, T o2);

當須要對一個對象數組進行排序時,Arrays.sort方法須要一個Comparator接口實例來指定排序的規則。假設有一個Person類,含有String nameint age兩個成員變量:

public class Person { 
    private String name;
    private int age;
    
    // 省略構造器、toString方法與Getter Setter 
}
複製代碼

傳統寫法

若是使用傳統的代碼對Person[]數組進行排序,寫法以下:

public class Demo05Comparator {
    public static void main(String[] args) {
      	// 原本年齡亂序的對象數組
        Person[] array = { 
            new Person("古力娜扎", 19),        	
            new Person("迪麗熱巴", 18),       		
            new Person("馬爾扎哈", 20) 
        };

      	// 匿名內部類
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        };
        Arrays.sort(array, comp); // 第二個參數爲排序規則,即Comparator接口實例

        for (Person person : array) {
            System.out.println(person);
        }
    }
}
複製代碼

這種作法在面向對象的思想中,彷佛也是「理所固然」的。其中Comparator接口的實例(使用了匿名內部類)表明了「按照年齡從小到大」的排序規則。

代碼分析

下面咱們來搞清楚上述代碼真正要作什麼事情。

  • 爲了排序,Arrays.sort方法須要排序規則,即Comparator接口的實例,抽象方法compare是關鍵;
  • 爲了指定compare的方法體,不得不須要Comparator接口的實現類;
  • 爲了省去定義一個ComparatorImpl實現類的麻煩,不得不使用匿名內部類;
  • 必須覆蓋重寫抽象compare方法,因此方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;
  • 實際上,只有參數和方法體纔是關鍵

Lambda寫法

public class Demo06ComparatorLambda {
    public static void main(String[] args) {
        Person[] array = {
          	new Person("古力娜扎", 19),
          	new Person("迪麗熱巴", 18),
          	new Person("馬爾扎哈", 20) };

        Arrays.sort(array, (Person a, Person b) -> {
          	return a.getAge() - b.getAge();
        });

        for (Person person : array) {
            System.out.println(person);
        }
    }
}
複製代碼

省略格式:

省略規則

在Lambda標準格式的基礎上,使用省略寫法的規則爲:

  1. 小括號內參數的類型能夠省略;
  2. 若是小括號內有且僅有一個參,則小括號能夠省略;
  3. 若是大括號內有且僅有一個語句,則不管是否有返回值,均可以省略大括號、return關鍵字及語句分號。

備註:掌握這些省略規則後,請對應地回顧本章開頭的多線程案例。

可推導便可省略

Lambda強調的是「作什麼」而不是「怎麼作」,因此凡是能夠推導得知的信息,均可以省略。例如上例還可使用Lambda的省略寫法:

Runnable接口簡化:
1. () -> System.out.println("多線程任務執行!")
Comparator接口簡化:
2. Arrays.sort(array, (a, b) -> a.getAge() - b.getAge());
複製代碼

1.4 Lambda的前提條件

Lambda的語法很是簡潔,徹底沒有面向對象複雜的束縛。可是使用時有幾個問題須要特別注意:

  1. 使用Lambda必須具備接口,且要求接口中有且僅有一個抽象方法。 不管是JDK內置的RunnableComparator接口仍是自定義的接口,只有當接口中的抽象方法存在且惟一時,纔可使用Lambda。
  2. 使用Lambda必須具備接口做爲方法參數。 也就是方法的參數或局部變量類型必須爲Lambda對應的接口類型,才能使用Lambda做爲該接口的實例。

備註:有且僅有一個抽象方法的接口,稱爲「函數式接口」。

第二章 函數式接口

2.1 概述

函數式接口在Java中是指:有且僅有一個抽象方法的接口

函數式接口,即適用於函數式編程場景的接口。而Java中的函數式編程體現就是Lambda,因此函數式接口就是能夠適用於Lambda使用的接口。只有確保接口中有且僅有一個抽象方法,Java中的Lambda才能順利地進行推導。

備註:從應用層面來說,Java中的Lambda能夠看作是匿名內部類的簡化格式,可是兩者在原理上不一樣。

格式

只要確保接口中有且僅有一個抽象方法便可:

修飾符 interface 接口名稱 {
    public abstract 返回值類型 方法名稱(可選參數信息);
    // 其餘非抽象方法內容
}
複製代碼

因爲接口當中抽象方法的public abstract是能夠省略的,因此定義一個函數式接口很簡單:

public interface MyFunctionalInterface {	
	void myMethod();
}
複製代碼

FunctionalInterface註解

@Override註解的做用相似,Java 8中專門爲函數式接口引入了一個新的註解:@FunctionalInterface。該註解可用於一個接口的定義上:

@FunctionalInterface
public interface MyFunctionalInterface {
	void myMethod();
}
複製代碼

一旦使用該註解來定義接口,編譯器將會強制檢查該接口是否確實有且僅有一個抽象方法,不然將會報錯。不過,即便不使用該註解,只要知足函數式接口的定義,這仍然是一個函數式接口,使用起來都同樣。

2.2 經常使用函數式接口

JDK提供了大量經常使用的函數式接口以豐富Lambda的典型使用場景,它們主要在java.util.function包中被提供。前文的MySupplier接口就是在模擬一個函數式接口:java.util.function.Supplier<T>。其實還有不少,下面是最簡單的幾個接口及使用示例。

Supplier接口

java.util.function.Supplier<T>接口,它意味着"供給" , 對應的Lambda表達式須要「對外提供」一個符合泛型類型的對象數據。

抽象方法 : get

僅包含一個無參的方法:T get()。用來獲取一個泛型參數指定類型的對象數據。

public class Demo08Supplier {
    private static String getString(Supplier<String> function) {
      	return function.get();
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        System.out.println(getString(() -> msgA + msgB));
    }
}
複製代碼

求數組元素最大值

使用Supplier接口做爲方法參數類型,經過Lambda表達式求出int數組中的最大值。提示:接口的泛型請使用java.lang.Integer類。

代碼示例:

public class DemoIntArray {
    public static void main(String[] args) {
        int[] array = { 10, 20, 100, 30, 40, 50 };
        printMax(() -> {
            int max = array[0];
            for (int i = 1; i < array.length; i++) {
                if (array[i] > max) {              
                  	max = array[i];
                }
            }
            return max;
        });
    }

    private static void printMax(Supplier<Integer> supplier) {
        int max = supplier.get();
        System.out.println(max);
    }
}
複製代碼

Consumer接口

java.util.function.Consumer<T>接口則正好相反,它不是生產一個數據,而是消費一個數據,其數據類型由泛型參數決定。

抽象方法:accept

Consumer接口中包含抽象方法void accept(T t),意爲消費一個指定泛型的數據。基本使用如:

import java.util.function.Consumer;

public class Demo09Consumer {
    private static void consumeString(Consumer<String> function , String str) {
      	function.accept(str);
    }

    public static void main(String[] args) {
        consumeString(s -> System.out.println(s), "後端跟我學");
      
    }
}
複製代碼

Function接口

java.util.function.Function<T,R>接口用來根據一個類型的數據獲得另外一個類型的數據,前者稱爲前置條件,後者稱爲後置條件。有進有出,因此稱爲「函數Function」。

抽象方法:apply

Function接口中最主要的抽象方法爲:R apply(T t),根據類型T的參數獲取類型R的結果。使用的場景例如:將String類型轉換爲Integer類型。

public class Demo11FunctionApply {
    private static void method(Function<String, Integer> function, Str str) {
        int num = function.apply(str);
        System.out.println(num + 20);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s) , "10");
    }
}
複製代碼

Predicate接口

有時候咱們須要對某種類型的數據進行判斷,從而獲得一個boolean值結果。這時可使用java.util.function.Predicate<T>接口。

抽象方法:test

Predicate接口中包含一個抽象方法:boolean test(T t)。用於條件判斷的場景,條件判斷的標準是傳入的Lambda表達式邏輯,只要字符串長度大於5則認爲很長。

public class Demo15PredicateTest {
    private static void method(Predicate<String> predicate,String str) {
        boolean veryLong = predicate.test(str);
        System.out.println("字符串很長嗎:" + veryLong);
    }

    public static void main(String[] args) {
        method(s -> s.length() > 5, "HelloWorld");
    }
}
複製代碼

後續連載文章, 敬請觀看:

觀看更多文章,請移步至 如何學習Java技術生態(分佈式微服務),霈哥有話說!


若對你和身邊的朋友有幫助, 抓緊關注 IT霈哥 點贊! 點贊! 點贊! 評論!收藏! 分享給更多的朋友共同窗習交流, 天天持續更新離不開你的支持!

歡迎關注個人B站,未來會發布文章同步視頻~~~ 歡迎關注個人公衆號,獲取更多資料~~~

相關文章
相關標籤/搜索