筆記22-Lambda&方法參照

2020-08-09 14:24:27

Lambda表達式

函數語言程式設計思想概述

在數學中,函數就是有輸入量,輸出量的一套計算方案,也就是"拿數據做操作"
物件導向思想強調"必須通過物件的形式來做事情"
函數式思想則儘量忽略物件導向的複雜語法 : 「強調做什麼,而不是以什麼形式去做」
而我們要學習的Lambda表達式就是函數式思想的體現

體驗Lambda表達式

需求: 啓動一個執行緒,在控制檯輸出一句話,多執行緒程式啓動了
方式1:

  • 定義一個myrunable類實現Runnable介面,重寫run()方法
  • 建立myrunnable類的物件
  • 建立thread類的物件,把myrunnable的物件作爲構造參數傳遞
  • 啓動執行緒

方式2:

  • 匿名內部類的方式改進

方式3

  • Lambda表達式的方式改進

Lambda表達式的標準格式

匿名內部類中重新run()方法的程式碼分析
在这里插入图片描述

  • 方法形式參數爲空,說明呼叫方法時不需要傳遞參數
  • 方法返回值型別爲void ,說明方法執行沒有結果返回
  • 方法體中的內容,是我們具體要做的事情

Lambda表達式的程式碼分析
在这里插入图片描述

  • (): 裏面沒有內容,可以看成是方法形式參數爲空
  • ->:用箭頭指向後面要做的事情
  • { } :包含一段程式碼,我們稱之爲程式碼塊,可以看成是方法體中的內容

組成Lambda表達式的三要素: 形式參數,箭頭,程式碼塊

Lambda表達式的標準格式

Lambda表達式的格式

  • 格式: (形式參數)->{程式碼塊}
  • 形式參數: 如果有多個參數,參數之間用逗號隔開,如果沒有參數,留空即可
  • -> : 由英文中畫線和大於符號組成,固定寫法,代表指向動作
  • 程式碼塊: 是我們具體要做的事情,也就是以前我們寫的方法體內容

Lambda表達式的練習

Lambda表達式的使用前提

  • 有一個介面
  • 介面中有且僅有一個抽象方法

練習1:

  • 定義一個介面(Eatable),裏面定義一個抽象方法: void eat();
  • 定義一個測試類(EatableDemo),在測試類中提供兩個方法
    一個方法是: useEatable(Eatable e)
    一個方法是主方法, 在主方法中呼叫useEatable方法
package com.itdemo_29;

public class EatableDemo {
    public static void main(String[] args) {
        //在主方法中呼叫useEatable方法
        Eatable e = new EatableImpl();
        useEatable(e);

        //匿名內部類
        useEatable(new Eatable() {
            @Override
            public void eat() {
                System.out.println("一天一蘋果");
            }
        });

        //Lambda表達式
        useEatable(()->{
            System.out.println("醫生遠離我");
        });
    }

    private static void useEatable(Eatable e) {
        e.eat();
    }
}

練習2

  • 定義一個介面(Flyable),裏面定義一個抽象方法: void fly(String s);
  • 定義一個測試類(FlyableDemo),在測試類中提供兩個方法
    一個方法是useFlyable(Flyable f)
    一個方法是主方法: 在主方法中呼叫useFlyable方法
public interface Flyable {
    void fly(String s);
}


package com.itdemo_29;

public class FlyableDemo {
    public static void main(String[] args) {
        //在主方法中呼叫useFlyable方法
        Flyable f = new FlyableImpl();
        useFlyable(f);


        //匿名內部類
        useFlyable(new Flyable() {
            @Override
            public void fly(String s) {
                System.out.println(s);
                System.out.println("自駕遊");
            }
        });
        System.out.println("-------");
        //Lambda
        useFlyable((String s)->{
            System.out.println(s);
            System.out.println("日韓遊");
        });

    }

    private static void useFlyable(Flyable f){
        f.fly("風和日麗");
    }
}
class FlyableImpl implements Flyable{
    @Override
    public void fly(String s) {
        System.out.println("郊遊");
    }
}


練習3:

  • 定義一個介面,(Addable) ,裏面定義一個抽象方法,int add(int x,int y);
  • 定義一個測試類(AddableDemo),在測試類匯中提供兩個方法
    一個方法是: useAddable(Addable a)
    一個方法是主方法,在主方法中呼叫useAddablfe方法
public interface Addable {
    int add(int x,int y);
}



package com.itdemo_29;

public class AddableDemo {
    public static void main(String[] args) {
//        Lambda
        useAddable((int x,int y)->{
            return x+y;
        });
    }
    private static void useAddable(Addable a){
        int sum = a.add(10, 29);
        System.out.println(sum);
    }
}

Lambda表達式的省略模式

省略的規則

  • 參數型別可以省略,但是有多個參數的情況下,不能只省略一個
  • 如果參數有且僅有一個,那麼小括號可以省略
  • 如果程式碼塊的語句只有一條,可以省略大括號和分號,和return關鍵字
public interface Addable {
    int add(int x,int y);
}


public interface Flyable {
    void fly(String s);
}



package com.itdemo_29;

public class LambdaDemo01 {
    public static void main(String[] args) {
        useFlyable((String s)->{
            System.out.println(s);
        });
        //如果參數只有一個,小括號可省略,參數型別可省略
        useFlyable(s->{
            System.out.println(s);
        });
        ///如果程式碼塊的語句只有一條,可以省略大括號和分號
        useFlyable(s-> System.out.println(s));

        useAddable((int x,int y)->{
            return x+y;
        });
//參數型別要省略,都必須省略
        useAddable((x,y)->{
            return x+y;
        });
        //如果程式碼塊的語句只有一條,可以省略大括號和分號,如果有return,return也要省略掉 
        useAddable((x,y)->x+y);

    }
    private static void useFlyable(Flyable f){
        f.fly("風和日麗");
    }
    private static void useAddable(Addable a){
        int sum = a.add(10,20);
        System.out.println(sum);
    }
}

Lambda表達式的注意事項

  • 使用Lambda必須要有介面,並且要求介面中有且僅有一個抽象方法
  • 必須有上下文環境,才能 纔能推導出Lanbda對應的介面
    • 根據區域性變數的賦值得知Lambda對應的介面
      Runnable r =() ->System.out.println(「Lambda表達式」);
    • 根據呼叫方法的參數得知Lambda對應的介面
      new Thread(()->System.out.println(「Lambda表達式」)).start();
package com.itdemo_29;

public class LambdaDemo02 {
    public static void main(String[] args) {
        useInter(()->{
            System.out.println("好好學習");
        });
        //使用Lambda必須有介面, 並且要求介面中有且僅有一個抽象方法
        useInter(()-> System.out.println("好好學習"));

        //必須有上下文環境,才能 纔能推導出Lambda對應的介面
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名內部類");
            }
        }).start();

        Runnable r = ()-> System.out.println("lambda表達式");
        new Thread(r).start();

        new Thread(()-> System.out.println("lambda表達式")).start();
    }
    private static void useInter(Inter i){
        i.show();
    }
}

Lambda表達式和匿名內部類的區別

所需型別不同

  • 匿名內部類: 可以是介面,可以是抽象類,可以是具體類
  • Lambda表達式: 只能是介面

使用限制不同

  • 如果介面中有且僅有一個抽象方法,可以使用Lambda表達式,也可以使用匿名內部類
  • 如果介面中多了一個抽象方法,只能使用匿名內部類,而不能使用Lambda表達式

實現原理不同

  • 匿名內部類: 編譯之後,產生一個單獨的.class位元組碼檔案
  • Lambda表達式: 編譯之後,沒有一個單獨的.class位元組碼檔案,對應的位元組碼會在執行時候動態生成
public abstract class Animal {
    public abstract void method();
}

public interface Inter {
    void show();
//    void show2();
}

public class Student {
    public void study(){
        System.out.println("愛學習");
    }
}




package com.itdemo_29;

public class LambdaDemo03 {
    public static void main(String[] args) {
        //匿名內部類
        useInter(new Inter() {
            @Override
            public void show() {
                System.out.println("介面");
            }
        });
//        useAnimal(new Animal() {
//            @Override
//            public void method() {
//                System.out.println("抽象類");
//            }
//        });
//        useStudent(new Student(){
//            @Override
//            public void study() {
//                System.out.println("具體類");
//            }
//        });

        //lanbda
//        useInter(()-> System.out.println("介面"));
//        useAnimal(()-> System.out.println("抽象");//是介面型別
//        useStudent(()-> System.out.println("具體類"));
        //當有多個介面
//        useInter(()-> System.out.println("介面"));
//        useInter(new Inter() {
//            @Override
//            public void show() {
//                System.out.println("show");
//            }
//
//            @Override
//            public void show2() {
//                System.out.println("show2");
//            }
//        });



    }
    private static void useStudent(Student s){
        s.study();
    }
    private static void useAnimal(Animal a){
        a.method();
    }
    private static void useInter(Inter i){
        i.show();
    }
}

介面的組成更新

介面組成更新概述

介面的組成

  • 常數
    public static final
  • 抽象方法
    public abstract
  • 預設方法(java 8)
  • 靜態方法(java 8)
  • 私有方法(java 9)

介面中預設方法

  • 格式:
    public default 返回值型別 方法名(參數列表){ }
  • 範例
public default void show3(){
}
  • 注意事項
    • 預設方法不是抽象方法,所以不強制被重寫,但是可以被重寫,重寫的時候去掉default關鍵字
    • public 可以省略,default 不能被省略
public interface MyInterface {
    void show1();
    void show2();
    //預設方法
    default void show3(){
        System.out.println("show3");
    }

}


public class MyInterfaceDemo {
    public static void main(String[] args) {
        MyInterface my = new MyInterfaceImplOne();
        my.show1();
        my.show2();
        my.show3();
    }
}




public class MyInterfaceImplOne implements MyInterface{
    @Override
    public void show1() {
        System.out.println("One show1");
    }

    @Override
    public void show2() {
        System.out.println("One show2");
    }

    @Override
    public void show3() {
        System.out.println("One show3");
    }
}


public class MyInterfaceImplTwe implements MyInterface{
    @Override
    public void show1() {
        System.out.println("Two show1");
    }

    @Override
    public void show2() {
        System.out.println("Two show2");
    }
}


介面中靜態方法

介面中靜態方法的定義格式

  • 格式: public static 返回值型別 方法名 (參數列表){}
  • 範例
public static void test(){}

介面中靜態方法的注意事項

  • 靜態方法只能通過介面名呼叫,不能通過實現類名或者物件名呼叫
  • public 可以省略,static不可以省略

public interface Flyable {
    public static void  test(){
        System.out.println("Flyable的靜態方法");
    }
}


public interface Inter {
    //抽象方法
    void show();

    default void mothod(){
        System.out.println("Inter中的預設方法");
    }
    static void test(){
        System.out.println("Inter中的靜態方法");
    }
}


public class InterImpl implements Inter{
    @Override
    public void show() {
        System.out.println("實現類中show");
    }

}



public class InterDemo {
    public static void main(String[] args) {
        //按照多型的方式建立物件使用
        Inter i =new InterImpl();
        i.show();
        i.mothod();
        //靜態方法不能通過實現類名或者物件呼叫
//        i.test();
//        InterImpl.test();
        //只能通過介面名呼叫
        Inter.test();
        Flyable.test();
    }
}

介面中私有方法

Java 9 新增了帶方法的私有方法,這其實在Java 8中就埋下了伏筆;Java8允許在介面中定義帶方法體的預設方法和靜態方法,這樣可能會引發一個問題: 當兩個預設方法或者靜態方法中包含一段相同的程式碼實現時,程式必然考慮將這段實現程式碼抽取成一個共性方法,而這個共性方法是不需要讓別人使用的,因此用私有給隱藏起來,這就是java9增加是由方法的必然性

介面中私有方法的定義格式:

  • 格式1 :private 返回值型別 方法名(參數列表){}
  • 範例1: private void show(){}
  • 格式2: private static 返回值型別 方法名 (參數列表){}
  • 範例2: private static void method(){}

介面中私有方法的注意事項

  • 預設方法可以呼叫私有靜態方法和非靜態方法
  • 靜態方法只能呼叫私有的靜態方法
public interface Inter01 {
    //預設方法
    default void show1(){
        System.out.println("show1開始執行");
//        System.out.println("初級工程師");
//        System.out.println("中級工程師");
//        System.out.println("高階工程師");
//        show();
        method();
        System.out.println("show1結束執行");
    }
    default void show(){
        System.out.println("初級工程師");
        System.out.println("中級工程師");
        System.out.println("高階工程師");
    }
    //靜態方法
    static void method1() {
        System.out.println("method1開始執行");
//        System.out.println("初級工程師");
//        System.out.println("中級工程師");
//        System.out.println("高階工程師");
//        show();
        method();
        System.out.println("method1結束執行");
    }
    static void method(){
        System.out.println("初級工程師");
        System.out.println("中級工程師");
        System.out.println("高階工程師");
    }
}




public class InterImpl01 implements Inter01{

}


public class InterDemo01 {
    public static void main(String[] args) {
        //按照多型的方式建立物件並使用
        Inter01 i = new InterImpl01();
        i.show1();

        Inter01.method1();

    }
}

方法參照

體驗方法參照

在使用Lambda表達式的時候, 我們實際上傳遞進去的程式碼就是一種解決方案: 拿參數做操作
那麼考慮一種情況: 如果我們在Lambda中所指定的操作方案,已經有地方存在相同的方案,那麼是否有必要在寫重複邏輯呢?
答案肯定是沒必要的
我們又是如何使用已經存在的方案的呢?
是通過方法參照的來使用已經存在的方案

package com.itdemo_30;

import com.itdemo_26.SystemOutDemo;

public class PrintableDemo {
    public static void main(String[] args) {
        usePrintable((String s)->{
            System.out.println(s);
        });

        //簡化
        usePrintable(s-> System.out.println(s));

        //方法參照符::
        usePrintable(System.out::println);
    }
    private static void usePrintable(Printable p){
        p.printString("aishengho");
    }
}

方法參照符

方法參照符

  • :: 該符號爲參照運算子,而它所在的表達式被稱爲方法參照

回顧一下我們在體驗方法參照中的程式碼

  • Lambda表達式: usePrintable(s->System.out.println(s));
    分析: 拿到參數s之後通過Lambda表達式,傳遞給System.out.println方法去處理
  • 方法參照: usePrintable(System.out::println);
    分析: 直接使用System.out中的println方法來取代Lambda,程式碼更加簡潔

推導與省略

  • 如果使用Lambda,那麼根據"可推導就是可省略"的原則,無需指定參數型別,也無需指定的過載形式,它們都將被自動推導
  • 如果使用方法參照,也是同樣可以根據上下文進行推導
  • 方法參照是Lambda的孿生兄弟
public interface Printable01 {
   void printInt(int i);
}



public class PrintableDemo01 {
    public static void main(String[] args) {
        //使用lambda省略
        usePrintable(i -> System.out.println(i));

        //使用方法參照
        usePrintable(System.out::println);

    }

    private static void usePrintable(Printable01 p) {
        p.printInt(666);
    }

}

Lambda表達式支援的方法參照

常見參照方法

  • 參照類方法
  • 參照物件的實體方法
  • 參照類的實體方法
  • 參照構造器

參照類方法

參照類方法: 其實就是參照型別的靜態方法

  • 格式: 類名::靜態方法
  • 範例: Integer::parseInt
    Integer類的方法: public static int parseInt(String s)將此String轉換爲int型別數據

練習:

  • 定義一個介面(Converter),裏面定義一個抽象方法
    int convert(String s)
  • 定義一個測試類(ConverterDemo)在測試類中提供了兩個方法
    一個是useConverter(Converter c)
    一個方法是主方法,在主方法中調動useConverter方法
public interface Converter {
    int convert(String s);
}


public class ConverterDemo {
    public static void main(String[] args) {
        //在主方法中呼叫useConverter
        useConverter((String s)->{
            return Integer.parseInt(s);
        });

        //簡略
        useConverter(s->Integer.parseInt(s));

        //參照類方法
        useConverter(Integer::parseInt);
        //Lambda表達式被類方法替代時,它的形式參數全部傳遞給靜態方法作爲參數
        
    }

    private static void useConverter(Converter c){
        int number = c.convert("6666");
        System.out.println(number);
    }
}

參照物件的實體方法

參照物件的實體方法,其實就是參照類中的成員方法

  • 格式: 物件:: 成員方法
  • 範例: "HelloWorld "::toUpperCase
    String 類中的方法,public string toUpperCase() 將所有字元轉換爲大寫

練習:

  • 定義一個類(PrintString) , 裏面定義一個方法
    public void printUpper(String s):把字串參數變成大寫的數據,然後在控制檯輸出
  • 定義一個介面(Printer),裏面定義一個抽象方法
    void printUpperCase(String s)
  • 定義一個測試類(PrinterDemo),在測試類中提供兩個方法
    一個方法是: usePrinter(Printer p)
    一個方法是主方法: 在方法中呼叫usePrinter方法
public interface Printer {
    void printUpperCase(String s);
}


public class PrintString {
    public void printUpper(String s){
        String result = s.toUpperCase();
        System.out.println(result);
    }
}


package com.itdemo_30;

public class PrinterDemo {
    public static void main(String[] args) {
        usePrinter((String s)->{
            String result = s.toUpperCase();
            System.out.println(result);
        });

        usePrinter(s-> System.out.println(s.toUpperCase()));

        //參照物件的實體方法
        PrintString ps = new PrintString();
        usePrinter(ps::printUpper);

        //Lambda表達式被物件的實體方法替代的時候,它的形式參數全部傳遞給該方法作爲參數
    }
    private static void usePrinter(Printer p){
        p.printUpperCase("helloworld");
    }
}

參照構造器

參照構造器,其實就是參照構造方法

  • 格式: 類名::new
  • 範例: Student::new

練習:

  • 定義一個類(Student),裏面有兩個成員變數(name,age)
    並提供無參構造方法和帶參構造方法,以及成員變數的get和set方法
  • 定義一個介面(StudentBuilder),裏面定義一個抽象方法
    Student build(String name,int age);
  • 定義一個測試類(StudentDemo),在測試類中提供兩個方法
    一個方法是useStudentBuilder(StudentBuilder s)
    一個方法是主方法,在主方法中呼叫useStudentBuilder方法
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}



public interface StudentBuilder {
    Student build(String name,int age);
}



package com.itdemo_30;

public class StudnetDemo {
    public static void main(String[] args) {
        useStudentBuilder((String name,int age)->{
//            Student s = new Student(name,age);
//            return s;
            return new Student(name,age);
        });
        //參照型別
        useStudentBuilder((name,age)->new Student(name,age));

        //參照構造器
        useStudentBuilder(Student :: new);
        //Lambda表達式被構造器替代的時候,它的形式參數全部傳遞到構造器作爲參數
    }
    private static  void useStudentBuilder(StudentBuilder s){
        Student s1 = s.build("萌萌",23);
        System.out.println(s1.getName()+","+s1.getAge());
    }
}

使用說明
Lambda表達式被構造器替代的時候,它的形式參數全部傳遞給構造器作爲參數