Java 進階——JDK 8帶給你新的好喫的語法糖應用之Lambda表達式小結

引言

初識Lambda[ˈlæmdə]是在13年左右,是在另一門微軟的語言C#中的,當時就覺得Lambda很簡潔,可奇怪的是類似Scala這種JVM語言早已經支持Lambda了,而Java語言直到JDK 8才引入Lambda的支持,所以很多人可能一開始接觸的時候不太瞭解和不太習慣Lambda的寫法。

一、語法糖概述

語法糖(Syntactic sugar)也譯爲糖衣語法,是由英國計算機科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語,指在原有計算機語言的基礎上中添加某種語法且該語法對語言的原有功能沒有影響,但是可以更方便地供程序員使用,通常使用語法糖能夠增強代碼的可讀性和簡潔性的,從而減少程序代碼出錯的機會。語法糖並非是某一門語言所特有的,事實上很多語言都支持語法糖,也並不意味着同樣的功能使用語法糖就一定比普通寫法更高效。雖然JDK 1.8之前也有語法糖的應用,但JVM並不是直接支持這些語法糖,這些語法糖在編譯階段就會被還原成簡單的基礎語法結構(具體是在com.sun.tools.javac.main.JavaCompiler 的源碼中的 compile() 方法內調用 desugar() 方法)即解語法糖過程,典型的有以下的應用:

  • JDK 7 引入的switch語句支持 String 與枚舉作爲條件變量,原本Java中的switch語句只支持char、byte、short、int的(對於int類型,直接進行數值的比較;對於char類型則是比較其ascii碼),而引入語法糖之後,就支持String和枚舉類型,就是因爲在編譯成功後把原始的String條件變量,通過獲取hashCode()轉爲int整形變量,然後再去比較。

  • 自動裝箱與拆箱,編譯成功後裝箱(Java自動將基本類型值轉換成對應的對象)就是通過調用包裝器對象的valueOf方法來獲取對應的對象,而拆箱(將對象轉換成對應的基本類型則是通過調用包裝器對象的 xxxValue方法實現的。

  • 可變參數在被使用的時候,編譯成功後首先會創建一個數組,而數組的長度就是調用該方法是傳遞的實參的個數,再把參數值全部放到這個數組當中,最後把這個數組作爲參數傳遞到被調用的方法中。

  • 內部類,可理解爲外部類的一個普通成員,編譯成功後就會生成兩個獨立的.class文件,比如outer.java裏面定義了一個內部類inner,desugar之後就會生成兩個完全不同的.class文件了,分別是outer.class和outer$inner.class。所以內部類的名字完全可以和它的外部類名字相同。

  • 數值字面量,在Java 7中,數值字面量,不管是整數還是浮點數,都允許在數字之間插入任意多個下劃線,編譯後成功後就是把_刪除了。

以上僅僅是列出部分典型的語法糖應用。

二、Lambda表達式概述

Lambda表達式是語法糖應用的一種形式,可以把它簡單理解爲是一種可傳遞的匿名方法或匿名內部類的實現形式(但不宜認爲它是匿名方法或匿名內部類的語法糖),Lambda表達式中沒有方法(類)名稱,由形參列表方法主體返回值(可省)三部分構成,整個Lambda表達式可以看成一個普通變量進行賦值操作和傳參操作中,在JVM內部Lambda表達式會被編譯爲invokedynamic指令,每一個Lambda表達式的實現邏輯均會被封裝爲一個對應的靜態私有方法,只要存在Lambda表達式調用,編譯後便會生成一個內部類,所以Lambda中的this指針指向的是周圍的類(定義該Lambda表達式時所處的類)
在這裏插入圖片描述

1、Lambda基本語法

函數式接口的普通寫法經過去掉修飾符去掉方法名去掉返回值類型(編譯器自動推斷)、去掉參數類型(編譯器自動推斷)後,保留參數列表、方法體返回值,再通過Lambda(箭頭) 操作符 ->就可將函數式接口轉變爲Lambda表達式:形參列表方法體通過Lambda操作符連接起來,如下圖所示:

在這裏插入圖片描述
除了以上這種Lambda操作符的表現形式,還有一種特殊(當方法體對應的操作已有實現的方法時)的表現形式——方法引用,其基本語法是使用引用操作符 ::方法名和對象實例或類名連接起來,主要有三類:

  • 類名::靜態方法名
  • 類名::實例方法名
  • 對象::實例方法名

方法引用中還有一種特殊的形式——構造器引用類名::new,總之請記住:Lambda表達式的簡化對象是函數式接口,所以 Lambda 表達式都能隱式地賦值給其對應的函數式接口

2、函數式接口

函數式接口(Functional Interface)中有且只有一個抽象方法,換言之有且只有一個需要被實現的抽象方法的接口叫做函數式接口。JDK中爲了明確標識函數式接口引入了 @FunctionalInterface註解,同時也是爲了避免在原函數式接口中增加新的方法聲明導致其變成非函數式接口,如果你在使用了 @FunctionalInterface註解的接口裏聲明瞭多個方法則編譯失敗,也正是因爲函數式接口這一顯著特性使得Lambda中可以省略函數名(因爲一個接口只對應一個函數)

3、Lambda表達式的結構特點

  • 一個 Lambda 表達式可以有零個或多個形參。
  • 形參的類型既可以明確聲明,也可以省略不寫。
  • 所有形參均包含在圓括號內,形參之間用逗號連接。
  • Lambda方法體中調用方法的參數列表與返回值類型與函數式接口中抽象方法的參數列表和返回值類型一致
  • 空圓括號代表形參列表爲空。
  • 當只有一個參數且其類型可推導時,小括號()可省。
  • Lambda 表達式的主體(方法體)可包含零條或多條語句。
  • 如果 Lambda 表達式的主體只有一條語句,則花括號{}可省。匿名函數的返回類型與該主體表達式一致
  • 如果 Lambda 表達式的主體包含一條以上語句,則表達式必須包含在花括號內形成代碼塊,匿名函數的返回類型與代碼塊的返回類型一致,若沒有返回則爲空。

三、Lambda表達式的簡單應用

1、Runnable

在這裏插入圖片描述

2、Lambda表達式作爲參數傳遞

在這裏插入圖片描述

3、方法引用

import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * @author : Crazy.Mo
 */
public class FuncReference {
    public static void main(String[] args) {
        Supplier<Double> supplier = new Supplier<Double>() {
            @Override
            public Double get() {
                return Math.random();
            }
        };

        //Lambda操作符形式
        Supplier<Double> supplier2=()->Math.random();
        System.out.println("supplier2"+supplier2.get());
        //1、方法引用形式,類名::靜態方法名
        Supplier<Double> supplier3=Math::random;
        System.out.println("supplier3"+supplier3.get());

        Comparator<Integer> stringComparator = new Comparator<Integer>(){

            @Override
            public int compare(Integer s1,  Integer s2) {
                /**
                 *  你對一個函數式接口中的抽象方法重寫時,若傳遞兩個參數,其中一個參數是作爲調用者,另一個作爲實參
                 *  則可以使用方法引用來簡寫Lambda表達式
                 */
                return s1.compareTo(s2);
            }
        };
        Comparator<Integer> stringComparator2=(s1,s2)->s1.compareTo(s2);
        //2、方法引用,類名::實例方法名
        Comparator<Integer> stringComparator3=Integer::compareTo;
        stringComparator2.compare(66,88);
        stringComparator3.compare(66,88);

        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                PrintStream out = System.out;
                out.println(s);
            }
        };
        PrintStream out = System.out;
        Consumer<String> consumer2=(s)->out.println(s);
        //3、方法引用(沒有參數),對象名::實例方法名
        Consumer<String> consumer3=out::println;
        consumer2.accept("Lambda形式");
        consumer3.accept("對象::實例方法名形式");
    }
}

在這裏插入圖片描述

4、構造器引用形式

構造方法與函數式接口相結合並自動與函數式接口中方法兼容,可以把構造器引用賦值給定義的方法,構造器參數列表要與接口中抽象方法的參數列表一致。

    class MyClass {
        String s;
        public MyClass(String s) {
            this.s = s;
        }
    }

    /**
     * 定義與構造方法參數一致的函數式接口
     * @param <T>
     * @param <R>
     */
    @FunctionalInterface
    public interface MyFuncInterface<T, R> {
        R myNew(T t);
    }


    @Test
    public void main2() {

        //傳統的方式
        MyFuncInterface<String, MyClass> myFuncInterface1 = new MyFuncInterface<String, MyClass>() {
            @Override
            public MyClass myNew(String s) {
                return new MyClass(s);
            }
        };
        //Lamada方式---首先實現此方法
        MyFuncInterface<String, MyClass> myFuncInterface2 = (s) -> new MyClass(s);
        //構造器引用的方式,不管參數了列表了,簡化方法體
        MyFuncInterface<String, MyClass> myFuncInterface3 = MyClass::new;
        //得到MyClass對象
        MyClass myclass = myFuncInterface3.myNew("構造器引用形式");
        System.out.println(myclass.s);
    }

在這裏插入圖片描述

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