凱哥帶你從零學大數據系列之Java篇---第二十二章:Lambda表達式

課程重點:

1 函數式接口
   函數式接口的概念
   函數式接口的判斷
   @FunctionalInterface
2 lambda表達式的語法
   基礎的語法
   語法的精簡(參數、方法體)
3 函數引用
   靜態、非靜態、構造方法的引用
   對象方法的特殊引用

22.1. Lambda表達式的簡介

22.1.1. Lambda表達式的概念

lambda表達式, 是Java8的一個新特性, 也是Java8中最值得學習的新特性之一。

lambda表達式, 從本質來講, 是一個匿名函數。 可以使用使用這個匿名函數, 實現接口中的方法。 對接口進行非常簡潔的實現, 從而簡化代碼。

22.1.2. Lambda表達式的使用場景

通常來講, 使用lambda表達式, 是爲了簡化接口實現的。

關於接口實現, 可以有很多種方式來實現。 例如: 設計接口的實現類、 使用匿名內部類。 但是lambda表達式, 比這兩種方式都簡單。

/**
 * @Description
 */
public class Program {
    public static void main(String[] args) {
        // 無參、無返回值的函數式接口
        interfaceImpl();
    }

    private static void interfaceImpl() {
        // 1. 使用顯式的實現類對象
        SingleReturnSingleParameter parameter1 = new Impl();
        // 2. 使用匿名內部類實現
        SingleReturnSingleParameter parameter2 = new SingleReturnSingleParameter() {
            @Override
            public int test(int a) {
                return a * a;
            }
        };
        // 3. 使用lambda表達式實現
        SingleReturnSingleParameter parameter3 = a -> a * a;

        System.out.println(parameter1.test(10));
        System.out.println(parameter2.test(10));
        System.out.println(parameter3.test(10));
    }

    private static class Impl implements SingleReturnSingleParameter {

        @Override
        public int test(int a) {
            return a * a;
        }
    }
}

22.1.3. Lambda表達式對接口的要求

雖然說, lambda表達式可以在一定程度上簡化接口的實現。 但是, 並不是所有的接口都可以使用lambda表達式來簡潔實現的。

lambda表達式畢竟只是一個匿名方法。 當實現的接口中的方法過多或者多少的時候, lambda表達式都是不適用的。

lambda表達式,只能實現函數式接口

22.1.4. 函數式接口

22.1.4.1. 基礎概念

如果說, 一個接口中, 要求實現類必須實現的抽象方法, 有且只有一個! 這樣的接口, 就是函數式接口。

// 這個接口中, 有且只有一個方法, 是實現類必須實現的, 因此是一個函數式接口
interface Test1 {
    void test();
}
// 這個接口中, 實現類必須要實現的方法, 有兩個! 因此不是一個函數式接口
interface Test2 {
    void test1();
    void test2();
}
// 這個接口中, 實現類必須要實現的方法, 有零個! 因此不是一個函數式接口
interface Test3 {
    
}
// 這個接口中, 雖然沒有定義任何的方法, 但是可以從父接口中繼承到一個抽象方法的。 是一個函數式接口
interface Test4 extends Test1 {
    
}
// 這個接口, 雖然裏面定義了兩個方法, 但是defualt方法子類不是必須實現的。
// 因此, 實現類實現這個接口的時候, 必須實現的方法只有一個! 是一個函數式接口。
interface Test5 {
    void test5();
    default void test() {}
}
// 這個接口中的 toString 方法, 是Object類中定義的方法。
// 此時, 實現類在實現接口的時候, toString可以不重寫的! 因爲可以從父類Object中繼承到!
// 此時, 實現類在實現接口的時候, 有且只有一個方法是必須要重寫的。 是一個函數式接口!
interface Test6 {
    void test6();
    String toString();
}

 

思考題: 下面的兩個接口是不是函數式接口?

interface Test7 {
 String toString();
}

interface Test8 {
 void test();
 default void test1() {}
 static void test2() {}
 String toString();
}

22.1.4.2. @FunctionalInterface

是一個註解, 用在接口之前, 判斷這個接口是否是一個函數式接口。 如果是函數式接口, 沒有任何問題。 如果不是函數式接口, 則會報錯。 功能類似於 @Override。

@FunctionalInterface
interface FunctionalInterfaceTest {
    void test();
}

22.1.4.3. 系統內置的若干函數式接口

22.2. Lambda表達式的語法

22.2.1. Lambda表達式的基礎語法

lambda表達式, 其實本質來講, 就是一個匿名函數。 因此在寫lambda表達式的時候, 不需要關心方法名是什麼。

實際上, 我們在寫lambda表達式的時候, 也不需要關心返回值類型。

我們在寫lambda表達式的時候, 只需要關注兩部分內容即可: 參數列表 和 方法體

lambda表達式的基礎語法:

(參數) ->{
   方法體
};

參數部分 : 方法的參數列表, 要求和實現的接口中的方法參數部分一致, 包括參數的數量和類型。

方法體部分 : 方法的實現部分, 如果接口中定義的方法有返回值, 則在實現的時候, 注意返回值的返回。

-> : 分隔參數部分和方法體部分。

/**
 * @Description
 */
public class Syntax {
    public static void main(String[] args) {
        // 1. 無參、無返回值的方法實現
        NoneReturnNoneParameter lambda1 = () -> {
            System.out.println("無參、無返回值方法的實現");
        };
        lambda1.test();

        // 2. 有參、無返回值的方法實現
        NoneReturnSingleParameter lambda2 = (int a) -> {
            System.out.println("一個參數、無返回值方法的實現: 參數是 " + a);
        };
        lambda2.test(10);

        // 3. 多個參數、無返回值方法的實現
        NoneReturnMutipleParameter lambda3 = (int a, int b) -> {
            System.out.println("多個參數、無返回值方法的實現: 參數a是 " + a + ", 參數b是 " + b);
        };
        lambda3.test(10, 20);

        // 4. 無參、有返回值的方法的實現
        SingleReturnNoneParameter lambda4 = () -> {
            System.out.println("無參、有返回值方法的實現");
            return 666;
        };
        System.out.println(lambda4.test());

        // 5. 一個參數、有返回值的方法實現
        SingleReturnSingleParameter lambda5 = (int a) -> {
            System.out.println("一個參數、有返回值的方法實現: 參數是 " + a);
            return a * a;
        };
        System.out.println(lambda5.test(9));

        // 6. 多個參數、有返回值的方法實現
        SingleReturnMutipleParameter lambda6 = (int a, int b) -> {
            System.out.println("多個參數、有返回值的方法實現: 參數a是 " + a + ", 參數b是 " + b);
            return a * b;
        };
        System.out.println(lambda6.test(10, 20));
    }
}

22.2.2. Lambda表達式的語法進階

在上述代碼中, 的確可以使用lambda表達式實現接口, 但是依然不夠簡潔, 有簡化的空間。

22.2.2.1. 參數部分的精簡

  • 參數的類型
    • 由於在接口的方法中,已經定義了每一個參數的類型是什麼。 而且在使用lambda表達式實現接口的時候, 必須要保證參數的數量和類型需要和接口中的方法保持一致。 因此, 此時lambda表達式中的參數的類型可以省略不寫。
    • 注意事項:
      • 如果需要省略參數的類型, 要保證: 要省略, 每一個參數的類型都必須省略不寫。 絕對不能出現, 有的參數類型省略了, 有的參數類型沒有省略。
// 多個參數、無返回值的方法實現
 NoneReturnMutipleParameter lambda1 = (a, b) -> {
     System.out.println("多個參數、無返回值方法的實現: 參數a是 " + a + ", 參數b是 " + b);
 };
  • 參數的小括號
    • 如果方法的參數列表中的參數數量 有且只有一個 ,此時,參數列表的小括號是可以省略不寫的。
    • 注意事項:
      • 只有當參數的數量是一個的時候, 多了、少了都不能省略。
      • 省略掉小括號的同時, 必須要省略參數的類型。
// 有參、無返回值的方法實現
NoneReturnSingleParameter lambda2 = a -> {
    System.out.println("一個參數、無返回值方法的實現: 參數是 " + a);
};

22.2.2.2. 方法體部分的精簡

  • 方法體大括號的精簡
    • 當一個方法體中的邏輯, 有且只有一句的情況下, 大括號可以省略。
// 有參、無返回值的方法實現
NoneReturnSingleParameter lambda2 = a -> System.out.println("一個參數、無返回值方法的實現: 參數是 " + a);  
  • return的精簡
    • 如果一個方法中唯一的一條語句是一個返回語句, 此時在省略掉大括號的同時, 也必須省略掉return。
SingleReturnMutipleParameter lambda3 = (a, b) -> a + b;

22.3. 函數引用

lambda表達式是爲了簡化接口的實現的。 在lambda表達式中, 不應該出現比較複雜的邏輯。 如果在lambda表達式中出現了過於複雜的邏輯, 會對程序的可讀性造成非常大的影響。 如果在lambda表達式中需要處理的邏輯比較複雜, 一般情況會單獨的寫一個方法。 在lambda表達式中直接引用這個方法即可。

或者, 在有些情況下, 我們需要在lambda表達式中實現的邏輯, 在另外一個地方已經寫好了。 此時我們就不需要再單獨寫一遍, 只需要直接引用這個已經存在的方法即可。

函數引用: 引用一個已經存在的方法, 使其替代lambda表達式完成接口的實現。

22.3.1. 靜態方法的引用

  • 語法:
    • 類::靜態方法

 

  • 注意事項:
    • 在引用的方法後面, 不要添加小括號。
    • 引用的這個方法, 參數(數量、類型) 和 返回值, 必須要跟接口中定義的一致。

 

  • 示例:
/**
 * @Description
 */
public class Syntax1 {
    // 靜態方法的引用
    public static void main(String[] args) {
        // 實現一個多個參數的、一個返回值的接口
        // 對一個靜態方法的引用
        // 類::靜態方法
        SingleReturnMutipleParameter lambda1 = Calculator::calculate;
        System.out.println(lambda1.test(10, 20));
    }

    private static class Calculator {
        public static int calculate(int a, int b) {
            // 稍微複雜的邏輯:計算a和b的差值的絕對值
            if (a > b) {
                return a - b;
            }
            return b - a;
        }
    }
}

22.3.2. 非靜態方法的引用

  • 語法:
    • 對象::非靜態方法

 

  • 注意事項:
    • 在引用的方法後面, 不要添加小括號。
    • 引用的這個方法, 參數(數量、類型) 和 返回值, 必須要跟接口中定義的一致。

 

  • 示例:
/**
 * @Description
 */
public class Syntax2 {
    public static void main(String[] args) {
        // 對非靜態方法的引用,需要使用對象來完成
        SingleReturnMutipleParameter lambda = new Calculator()::calculate;
        System.out.println(lambda.test(10, 30));
    }

    private static class Calculator {
        public int calculate(int a, int b) {
            return a > b ? a - b : b - a;
        }
    }
}

22.3.3. 構造方法的引用

  • 使用場景

如果某一個函數式接口中定義的方法, 僅僅是爲了得到一個類的對象。 此時我們就可以使用構造方法的引用, 簡化這個方法的實現。

  • 語法

類名::new

  • 注意事項

可以通過接口中的方法的參數, 區分引用不同的構造方法。

  • 示例
/**
 * @Description
 */
public class Syntax3 {
    private static class Person {
        String name;
        int age;
        public Person() {
            System.out.println("一個Person對象被實例化了");
        }
        public Person(String name, int age) {
            System.out.println("一個Person對象被有參的實例化了");
            this.name = name;
            this.age = age;
        }
    }

    @FunctionalInterface
    private interface GetPerson {
        // 僅僅是希望獲取到一個Person對象作爲返回值
        Person test();
    }

    private interface GetPersonWithParameter {
        Person test(String name, int age);
    }

    public static void main(String[] args) {
        // lambda表達式實現接口
        GetPerson lambda = Person::new;     // 引用到Person類中的無參構造方法,獲取到一個Person對象
        Person person = lambda.test();

        GetPersonWithParameter lambda2 = Person::new;  // 引用到Person類中的有參構造方法,獲取到一個Person對象
        lambda2.test("xiaoming", 1);
    }
}

22.3.4. 對象方法的特殊引用

如果在使用lambda表達式,實現某些接口的時候。 lambda表達式中包含了某一個對象, 此時方法體中, 直接使用這個對象調用它的某一個方法就可以完成整體的邏輯。 其他的參數, 可以作爲調用方法的參數。 此時, 可以對這種實現進行簡化。

/**
 * @Description
 */
public class Syntax {
    public static void main(String[] args) {
        // 如果對於這個方法的實現邏輯,是爲了獲取到對象的名字
        GetField field = person -> person.getName();
        // 對於對象方法的特殊引用
        GetField field = Person::getName;

        // 如果對於這個方法的實現邏輯,是爲了給對象的某些屬性進行賦值
        SetField lambda = (person, name) -> person.setName(name);
        SetField lambda = Person::setName;

        // 如果對於這個方法的實現邏輯,正好是參數對象的某一個方法
        ShowTest lambda2 = person -> person.show();
        ShowTest lambda2 = Person::show;
    }
}

interface ShowTest {
    void test(Person person);
}

interface SetField {
    void set(Person person, String name);
}

interface GetField {
    String get(Person person);
}

class Person {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void show() {

    }
}

22.4. Lambda表達式需要注意的問題

這裏類似於局部內部類、匿名內部類,依然存在閉包的問題。

如果在lambda表達式中,使用到了局部變量,那麼這個局部變量會被隱式的聲明爲 final。 是一個常量, 不能修改值。

22.5. Lambda表達式的實例

22.5.1. 線程的實例化

Thread thread = new Thread(() -> {
    // 線程中的處理
});

22.5.2. 集合的常見方法

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "千鋒", "大數據", "好程序員", "嚴選", "高薪");

// 按照條件進行刪除
list.removeIf(ele -> ele.endsWith(".m"));
// 批量替換
list.replaceAll(ele -> ele.concat("!"));
// 自定義排序
list.sort((e1, e2) -> e2.compareTo(e1));
// 遍歷
list.forEach(System.out::println);

22.5.3. 集合的流式編程

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "千鋒", "大數據", "好程序員", "嚴選", "高薪");

list.parallelStream().filter(ele -> ele.length() > 2).forEach(System.out::println);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章