Java 基礎(二)| 使用 lambad 表達式的正確姿式

Lambda表達式

前言

爲跳槽面試作準備,今天開始進入 Java 基礎的複習。但願基礎很差的同窗看完這篇文章,能掌握 lambda 表達式,而基礎好的同窗權當複習,但願看完這篇文章可以起一點你的青澀記憶。html

1、什麼是 lambda 表達式

Java8 是咱們使用最普遍的穩定 Java 版本,lambda 就是其中最引人矚目的新特性。lambda 是一種閉包,它容許把函數當作參數來使用,是面向函數式編程的思想,可使代碼看起來更加簡潔。是否是聽得一臉懵逼?我舉個栗子你就明白了。前端

爛掉牙的例子,在沒有 lambda 時候,咱們是這樣寫的:java

// 內部類寫法
public class InnerClassMain {
    public static void main(String[] args) {
        //匿名內部類寫法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("內部類寫法");
            }
        }).start();
    }
}

有 lambda 以後,咱們就用 lambda 寫:python

// lambda 寫法
public class LambdaMain {
    
    public static void main(String[] args) {
        //lambda 寫法
        new Thread(() -> System.out.println("lambda寫法")).start();
    }
    
}

咱們應該知道,實現線程有兩種方法,一是繼承 Thread 類,二是實現 Runnable 接口。那這裏採用的就是後者,後者是一個函數式接口。linux

1.1 函數式接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

從 Runnable 源碼能夠看到,它是一個函數式接口。這類接口的特色是:用 @FunctionalInterface 註解修飾(主要用於編譯級錯誤檢查,加上該註解,當你寫的接口不符合函數式接口定義的時候,編譯器會報錯),有且只有一個抽象方法。在原生 JDk 中的這類接口就可使用 lambda 表達式。c++

上面的概念提到,把函數當作參數來使用。上面的 lambda 例子中,Thread 類的參數就是一個 Runnable 接口,lambda 就是實現這個接口並把它當作參數使用。因此上面的 () -> System.out.println("lambda寫法") 就是一個整個 lambda 表達式的參數(注意與後面的方法參數區分開,後面會講)。細品加粗這句話,能夠總結出,lambda 表達式就是建立某個類的函數式接口的實例對象。如:git

Runnable runnable = () -> System.out.println("lambda寫法");

2、爲何須要 lambda 表達式

明白了什麼是 lambda 表達式,那爲何要使用它呢?注意到使用 lambda 建立線程的時候,咱們並不關心接口名,方法名,參數名。咱們只關注他的參數類型,參數個數,返回值。因此緣由就是簡化代碼,提升可讀性github

3、如何使用 lambda表達式

3.1 lambda 語法

// 格式遵循: (接口參數)->表達式(具體實現的方法)
(paramters) -> expression 或 (parameters) ->{ expressions; }

lambda 語法例子

具體解釋,如上圖。此外,lambda 語法注意點:面試

  • 可選類型聲明:方法參數不須要聲明參數類型,編譯器能夠統一識別參數值。
  • 可選的參數圓括號:一個參數無需定義圓括號,但無參數或多個參數須要定義圓括號。
  • 可選的大括號:若是具體實現方法只有一個語句,就不須要使用中括號{}。
  • 可選的返回關鍵字:若是具體實現方法只有一個表達式,則編譯器會自動返回值,若是有多個表達式則,中括號須要指定明表達式返回了一個數值。

使用示例:算法

public class Example {

    // 定義函數式接口,只能有一個抽象接口,不然會報錯
    // 但願在編譯期檢出報錯,請加 @FunctionalInterface 註解
    public interface Hello {
        String hi();
    }

    public interface Hello2 {
        String hei(String hello);
    }

    public interface Hello3 {
        String greet(String hello, String name);
    }

    public static void main(String[] args) {

        // 入參爲空
        Hello no_param = () -> "hi, no param";
        Hello no_param2 = () -> {
            return "hi, no param";
        };

        System.out.println(no_param.hi());
        System.out.println(no_param2.hi());

        // 單個參數,一條返回語句,能夠省略大括號和 return
        Hello2 param = name -> name;
        Hello2 param2 = name -> {
            return name;
        };

        // 打印
        System.out.println(param.hei("hei, 一個優秀的廢人"));
        System.out.println(param2.hei("hei, 一個優秀的廢人"));

        // 多個參數
        Hello3 multiple = (String hello, String name) -> hello + " " + name;

        // 一條返回語句,能夠省略大括號和 return
        Hello3 multiple2 = (hello, name) -> hello + name;

        // 多條處理語句,須要大括號和 return
        Hello3 multiple3 = (hello, name) -> {
            System.out.println(" 進入內部 ");
            return hello + name;
        };

        // 打印
        System.out.println(multiple.greet("hello,", "祝2020脫單"));
        System.out.println(multiple2.greet("hello,", "祝2020脫單"));
        System.out.println(multiple3.greet("hello,", "祝2020脫單"));
    }
}

3.3 方法引用

看一個簡單的方法引用例子:

Consumer<String> sc = System.out::println;
sc.accept("一個優秀的廢人");

// 等效於
Consumer<String> sc2 = (x) -> System.out.println(x);
sc2.accept("一個優秀的廢人");

Consumer 函數式接口源碼:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

你可能有點懵,爲何能夠這樣寫?別急咱們分析一波:Consumer<T> 是一個函數式接口,抽象方法是 void accept(T t),參數都是 T。那咱們如今有這樣一個需求,我想利用這個接口的抽象方法,作一下控制檯打印。正常狀況下,咱們須要實現這個接口,實現它的抽象方法,來實現這個需求:

public class ConsumerImpl implements Consumer<String> {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
}

實現以後,這個抽象方法變具體了。做用就是控制檯打印,那就意味着抽象方法恰好能夠用實際方法: System.out.println(s) 來實現,因此咱們可使用方法引用。

總結:函數式接口的抽象方法實現剛好能夠經過調用一個實際方法來實現時,就能夠用方法引用。

方法引用的三種形式:

// 將抽象方法參數當作實際方法的參數使用
對象::實例方法 objectName::instanceMethod
// 將抽象方法參數當作實際方法的參數使用
類::靜態方法 ClassName::staticMethod
// 將方法參數的第一個參數當作方法的調用者,其餘的參數做爲方法的參數
類::實例方法  ClassName::instanceMethod

自定義一個方法類:

public class Method {
    // 靜態方法
    public static void StaticMethod(String name) {
        System.out.println(name);
    }

    // 實例方法
    public void InstanceMethod(String name) {
        System.out.println(name);
    }

    // 無參構造方法
    public Method() {
    }

    // 有參數構造
    public Method(String methodName) {
        System.out.println(methodName);
    }
}

測試用例:

public class MethodExample {

    public static void main(String[] args) {

        // 靜態方法引用--經過類名調用
        Consumer<String> consumerStatic = Method::StaticMethod;
        consumerStatic.accept("靜態方法");
        // 等價於
        Consumer<String> consumerStatic2 = (x) -> Method.StaticMethod(x);
        consumerStatic2.accept("靜態方法");

        System.out.println("--------------------------");
        //非靜態方法引用--經過實例調用
        Method method = new Method();
        Consumer<String> consumerInstance = method::InstanceMethod;
        consumerInstance.accept("對象的實例方法");
        // 等價於
        Consumer<String> consumerInstance2 = (x) -> method.InstanceMethod(x);
        consumerInstance2.accept("對象的實例方法");

        System.out.println("--------------------------");
        //ClassName::instanceMethod  類的實例方法:把表達式的第一個參數當成 instanceMethod 的調用者,其餘參數做爲該方法的參數
        BiPredicate<String, String> sbp = String::equals;
        System.out.println("類的實例方法 " + sbp.test("a", "A"));
        // 等效
        BiPredicate<String, String> sbp2 = (x, y) -> x.equals(y);
        System.out.println("類的實例方法 " + sbp2.test("a", "A"));
    }
}

輸出結果:

靜態方法
靜態方法
--------------------------
對象的實例方法
對象的實例方法
--------------------------
類的實例方法false
類的實例方法false

3.4 構造器引用

public class ConstructMethodExample {

    public static void main(String [] args) {
        // 構造方法方法引用--無參數(可使用方法引用)
        Supplier<Method> supplier = Method::new;
        System.out.println(supplier.get());
        // 等價於
        Supplier<Method> supplier2 = () -> new Method();
        System.out.println(supplier2.get());

        // 構造方法方法引用--有參數
        Function<String, Method> uf = name -> new Method(name);
        Method method = uf.apply("一個優秀的廢人");
        System.out.println(method.toString());
    }

}

3.5 變量做用域

lambda 表達式只能引用標記了 final 的外層局部變量,這就是說不能在 lambda 內部修改定義在域外的局部變量,不然會編譯錯誤。

public class VariableScopeTest {

    // 定義一個接口
    public interface Converter<T1, T2> {
        void convert(int i);
    }
    
    public static void main(String [] args) {

        // 定義爲 final 強制不能修改
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        // 輸出結果爲 3
        s.convert(2);  
    }

}

變量不聲明爲 final ,致使能夠修改外部變量報錯:

int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);

此外,在 Lambda 表達式當中不容許聲明一個與局部變量同名的參數或者局部變量

String first = "";  
// 同爲 first 變量名,編譯會出錯 
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());

4、十大 lambda 表達式示例

5、源碼地址

Github源碼地址:https://github.com/turoDog/review_java

最後

若是看到這裏,喜歡這篇文章的話,幫忙 " 轉發 "或者點個" 在看 ",行嗎?祝大家 2020 暴富。微信搜索「一個優秀的廢人」,歡迎關注。

回覆「1024」送你一套完整的 java、python、c++、go、前端、linux、算法、大數據、人工智能、小程序以及英語教程。

回覆「電子書」送你 50+ 本 java 電子書。

最全教程

相關文章
相關標籤/搜索