1. 程式人生 > >java-函數語言程式設計-函式式介面以及方法應用

java-函數語言程式設計-函式式介面以及方法應用

一、lambda表示式

    1、 Lambda表示式是匿名內部類的簡化寫法。

    Lambda標準格式:

    (引數型別 引數名) -> {

        方法體;

        return 返回值;    

    }

    省略規則:

        1. 小括號中的引數型別可以省略。

        2. 如果小括號中只有一個引數,那麼可以省略小括號

        3. 如果大括號中只有一條語句,那麼可以省略大括號,return,以及;

    使用前提:

        1. 必須有介面, 介面中有且僅有一個需要被重寫的抽象方法。

        2. 必須支援上下文推導。 要麼有介面作為引數,要麼有一個變數接收這個Lambda表示式.

    函數語言程式設計: 可推導就是可省略

注:此處省略了persion物件的程式碼

public class Demo01Lambda {

    public static void main(String[] args) {

        //使用Lambda表示式實現多執行緒

        new Thread(() -> {

            System.out.println("Lambda標準格式執行多執行緒");

        }).start();

        //使用Lambda省略格式實現多執行緒

        new Thread(() -> System.out.println("Lambda省略格式執行多執行緒")).start();

    }

}

package cn.itcast.demo01;

import java.util.ArrayList;

import java.util.Collections;

/*

    建立集合,儲存Person,並且使用比較器排序方式對集合中的Person物件按照年齡升序排序

*/

public class Demo02Lambda {

    public static void main(String[] args) {

        //建立集合

        ArrayList<Person> list = new ArrayList<>();

        //新增Person

        list.add(new Person("大冪冪", 30));

        list.add(new Person("王思聰", 28));

        list.add(new Person("王健林", 40));

        //使用比較器排序對集合中的Person按照年齡升序排序

        /*

        Collections.sort(list, new Comparator<Person>() {

            @Override

            public int compare(Person o1, Person o2) {

                return o1.getAge() - o2.getAge();

            }

        });

        */

        Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());

        //列印集合

        System.out.println(list);

    }

}

二、函式式介面

    1、概念:如果一個介面中有且僅有一個需要被重寫的抽象方法,那麼這個介面就是函式式介面。

    2、函式式介面的使用:

        (1)當做一個普通介面去使用(給其他類實現)。

        (2)當做 lambda表示式的使用前提去使用(使用lambda表示式必須的使用函式式介面)。

有一個註解叫做:@ Functionallnterface,真個註解可以驗證是否是函式式介面,這個註解在函式的上面,如果某個介面不是函式式介面,那麼加上這個註解會報錯。

   這個註解僅僅用來驗證一個介面是否是一個函式式介面,如果沒有這個註解,只要某個介面滿足函式式介面的條件,那麼這個介面就是一個函式式介面。

@FunctionalInterface

public interface MyInterface {

    void method();

}

請定義一個函式式介面 Eatable ,內含抽象 eat 方法,沒有引數或返回值。使用該介面作為方法的引數,並進而

    通過Lambda來使用它

public interface Eatable {

    //eat方法

    void eat();

}

public class Demo01LambdaTest {

    public static void main(String[] args) {

        method(() ->  System.out.println("使用Lambda表示式吃飯飯,香香噠"));

    }

    //引數是一個函式式介面。

    //引數如果是函式式介面,那麼可以傳遞Lambda表示式

    public static void method(Eatable eatable) {

        eatable.eat();

    }

}

請定義一個函式式介面 Sumable ,內含抽象 sum 方法,可以將兩個int數字相加返回int結果。使用該介面作為方法的引數,並進而通過Lambda來使用它。

@FunctionalInterface

public interface Sumable {

    int sum(int a, int b);

}

public class Demo02LambdaTest {

    public static void main(String[] args) {

        method(new Sumable() {

            @Override

            public int sum(int a, int b) {

                return a + b;

            }

        });

        //呼叫method方法,傳遞Lambda表示式

        method((a, b) -> a + b);

    }

    public static void method(Sumable sumable) {

        int result = sumable.sum(10, 20);

        System.out.println("result:" + result);

    }

}

三、使用函式式介面列印日誌,優化效能浪費問題

    (1)傳統方式列印日誌

    步驟:

        1. 定義了三個字串

        2. 對這三個字串進行了拼接,並且當做引數傳遞給了printLog方法。

        3. 進行判斷,如果日誌等級滿足要求,那麼就列印日誌資訊。

                    如果日誌等級不滿足要求,那麼就不列印。

    如果不滿足要求,那麼就不會列印日誌資訊,但是之前已經把日誌資訊拼接好了,拼接好之後卻沒有使用

    這樣就產生了效能浪費。

public class Demo01Log {

    public static void main(String[] args) {

        //定義三個字串

        String s1 = "hello";

        String s2 = "world";

        String s3 = "java";

        //呼叫printLog方法,列印日誌資訊

        printLog(1, s1 + s2 + s3);

    }

    //用來列印日誌資訊

    //引數給兩個,一個是日誌等級。 第二個引數是日誌資訊

    public static void printLog(int level, String msg) {

        //進行判斷,如果日誌等級是3,那麼就列印日誌資訊

        if (level == 3) {

            System.out.println(msg);

        }

    }

}

(2)使用Lambda表示式解決效能浪費的問題

    如果日誌等級滿足要求,那麼就進行拼接,然後列印這個日誌資訊。

    如果日誌等級不滿足要求,那麼就不拼接了。

    Lambda表示式是作為介面中抽象方法的內容存在的。 所以當通過介面呼叫抽象方法時,才會

    執行對應的Lambda表示式。

public class Demo02LogLambda {

    public static void main(String[] args) {

        //建立三個字串

        String s1 = "hello";

        String s2 = "world";

        String s3 = "java";

        //呼叫method方法,根據日誌等級列印日誌資訊

        method(3, () -> {

            String msg = s1 + s2 + s3;

            System.out.println(msg);

        });

    }

    //第一個引數為日誌等級,第二個引數為用來列印日誌的函式式介面。

    public static void method(int level, MessageBuilder messageBuilder) {

        if (level == 1) {

            messageBuilder.printMsg();

        }

    }

}

.//定義一個函式式介面

public interface MessageBuilder {

    void printMsg();

}

四、Lambda表示式的六種使用方式

1、lambda表示式作為方法引數

//lambda表示式做為方法引數

public class Demo01LambdaParam {

    public static void main(String[] args) {

        new Thread(() -> System.out.println("使用Lambda表示式執行了執行緒")).start();

    }

}

2、Lambda表示式作為返回值型別

/*

    Lambda表示式作為返回值

*/

public class Demo02LambdaReturn {

    public static void main(String[] args) {

        //建立集合

        ArrayList<Person> list = new ArrayList<>();

        //呼叫list的add方法,向集合中新增Person物件

        list.add(new Person("滅絕師太", 50));

        list.add(new Person("金花婆婆", 20));

        list.add(new Person("金毛獅王", 30));

        //使用比較器排序,對學生根據年齡升序排序

        Collections.sort(list, getComparator());

        //列印結果

        System.out.println(list);

    }

    public static Comparator<Person> getComparator() {

        return (o1, o2) -> o1.getAge() - o2.getAge();

        /*

        return new Comparator<Person>() {

            @Override

            public int compare(Person o1, Person o2) {

                return o1.getAge() - o2.getAge();

            }

        };

        */

    }

}

(1) 請自定義一個函式式介面 MySupplier ,含有無引數的抽象方法 get 得到 Object 型別的返回值。並使用該函式式

    介面分別作為方法的引數和返回值。

public class Demo03LambdaTest {

    public static void main(String[] args) {

        //呼叫method方法,傳遞Lambda表示式

        //method(() -> "abc");

        method(getMySupplier());

    }

    public static void method(MySupplier mySupplier) {

        Object obj = mySupplier.get();

        System.out.println(obj);

    }

    //Lambda表示式作為方法的返回值.

    public static MySupplier getMySupplier() {

        return () -> "hello";

    }

}

public interface MySupplier {

    //沒有引數,有一個Object型別的返回值

    Object get();

}

public interface MySupplier{

       return get();

}

五、方法引用

1、  Lambda表示式做的事情是拿到引數之後, 【直接】對引數進行了列印

    此時Lambda表示式做的事情就和方法做的事情重疊了。

    Lambda表示式拿到引數之後直接進行了列印, 這樣的話其實就沒有必要再寫一遍引數了, 因為這個內容可以推導。

    如果Lambda表示式拿到引數之後,直接去幹了某些事情,那麼可以寫成另一種方式,另一種方式是Lambda的孿生兄弟: 方法引用

方法引用是Lambda表示式的一種簡化方式

 方法的使用格式     :: 兩個冒號

public class Demo01Lambda {

    public static void main(String[] args) {

        //lambda表示式, 方法引數多餘了,因為拿到引數之後直接對引數進行了列印

        method(str -> System.out.println(str));

        //方法引用

        //含義, 拿到引數之後,直接使用System.out的println方法列印這個引數.

        method(System.out::println);

    }

    public static void method(Printable printable) {

        printable.print("hello");

    }

}

public class Demo02Lambda {

    public static void main(String[] args) {

        //呼叫method方法,傳遞一個Lambda表示式

        method(num -> System.out.println(num));

        //換成方法引用

        method(System.out::println);

    }

    public static void method(PrintNumberable printNumberable) {

        printNumberable.printNumber(10);

    }

}

public interface Printable {

    void print(String str);

}

    public interface PrintNumberable {

    void printNumber(int num);

}

2、方法 引用的六種方式

   對於類來說有四種: 

(1)物件引用成員方法

(2)類名引用靜態方法

(3)supper應用父類方法

(4)this引用本類方法

對於構造器來說有兩種方式

(1)類的構造器引用

(2)陣列的構造器引用

1、物件引用成員方法: 

   格式;物件名::方法名

public class Demo01MethodRef {

    public static void main(String[] args) {

        //呼叫method方法,傳遞Lambda表示式。 將引數字串轉成大寫並列印

        //method(str -> System.out.println(str.toUpperCase()));

        MyClass myClass = new MyClass();

        //method(str -> myClass.printUpperStr(str));

        //換成方法引用

        //拿到引數之後,直接呼叫myClass物件的printUpperStr方法去對這個引數進行處理。

        method(myClass::printUpperStr);

    }

    //引數是函式式介面,呼叫的時候可以傳遞Lambda表示

    public static void method(Printable printable) {

        printable.print("hello");

    }

}

public class Demo02MethodRef {

    public static void main(String[] args) {

        //呼叫method方法,傳遞Lambda表示式

        //method(fileName -> System.out.println("Lambda表示式在七手八腳的處理這個檔案:" + fileName));

        //建立一個助手物件

        Assistant assistant = new Assistant();

        //method(fileName -> assistant.doFile(fileName));

        //方法引用

        method(assistant::doFile);

    }

    public static void method(Helper helper) {

        helper.help("工資流水.txt");

    }

}

public interface Helper {

    //幫助處理檔案

    void help(String fileName);

}

public class MyClass {

    //定義方法,接收一個字串的引數,並且將這個字串轉成大寫列印

    public void printUpperStr(String str) {

        System.out.println(str.toUpperCase());

    }

}

public interface Printable {

    void print(String str);

}

2、類名引用過成員方法

        格式   類名::靜態方法

public class Demo01MethodRef {

    public static void main(String[] args) {

        //呼叫method方法,傳遞Lambda表示式

        //method(num -> num > 0 ? num : -num);

        //Math數學工具類中有一個abs方法,可以直接求出一個數的絕對值

        method(num -> Math.abs(num));

        //拿到引數num,直接呼叫Math類的靜態方法abs對引數num進行了處理。

        method(Math::abs); //表示拿到引數之後,對引數直接通過Math類的abs方法進行處理。

    }

    public static void method(Calcable calcable) {

        int result = calcable.abs(-10);

        System.out.println(result);

    }

}

/*

    類名引用靜態方法的練習

*/

public class Demo02MethodRef {

    public static void main(String[] args) {

        //呼叫method方法,傳遞Lambda表示式,檢查引數字串是否為空(如果這個字串是null,或者是一個"",那麼就是空)

        //如果字串是空,那麼就返回true。不是空返回false

        //method(str -> str == null || str.equals(""));

        method(str -> StringUtils.isBlank(str));

        //改成方法引用

        method(StringUtils::isBlank); //拿到引數直接對引數使用StringUtils的靜態方法isBlank進行處理。

    }

    public static void method(Checker checker) {

        boolean flag = checker.check("");

        System.out.println("flag:" + flag);

    }

}

    3、super引用成員方法

        格式  super ::方法名

public class Demo01MethodRef {

    public static void main(String[] args) {

        //建立一個Student物件

        Student stu = new Student();

        stu.sayHello();

    }

}

@FunctionalInterface

public interface Greetable {

    void greet();

}

public class Person {

    public void sayHello() {

        System.out.println("雷猴");

    }

}

public class Student extends Person{

    public void sayHello() {

        //method(() -> System.out.println("雷猴"));

        //method(() -> super.sayHello());

        //改成方法引用。 使用super呼叫了父類的方法。 所以格式為: super::父類方法

        method(super::sayHello);

    }

    //定義方法,方法引數傳遞一個函式式介面

    public void method(Greetable greetable) {

        greetable.greet();

    }

}

4、this引用本類方法

public class Demo01MethodRef {

    public static void main(String[] args) {

        Man man = new Man();

        man.beHappy();

    }

}

public class Man {

    public void buyGift() {

        System.out.println("買一個500平米的大house");

    }

    public void marry(Richable richable) {

        richable.buy();

    }

    public void beHappy() {

        //marry(() -> System.out.println("買了一個500克拉的大鑽戒"));

        //marry(() -> buyGift());

        //使用this引用本類方法。  this::本類方法

        marry(this::buyGift);

    }

}

public interface Richable {

    void buy();

}

5、類的構造器使用

        格式  類名:: new 

public class Demo01MethodRef {

    public static void main(String[] args) {

        //呼叫method方法。 傳遞Lambda表示式

        //拿到引數姓名和年齡之後直接通過構造方法根據姓名和年齡建立了Person物件.所以可以換成方法引用.

        method((name, age) -> new Person(name, age));

        //使用類的構造器引用, 因為之前程式碼是在使用構造方法建立物件

        method(Person::new); //含義,拿到引數之後直接使用這些引數通過構造方法建立物件

    }

    //函式式介面當做方法引數

    public static void method(PersonBuilder personBuilder) {

        Person p = personBuilder.createPerson("大冪冪", 30);

        System.out.println(p);

    }

}

    6、陣列的構造器引用。

    格式:

        資料型別[]::new

public class Demo02MethodRef {

    public static void main(String[] args) {

        //呼叫method方法,傳遞lambda表示式,引數是多少,就建立一個長度是多少的陣列然後返回

        //method(len -> new int[len]); //拿到引數len之後直接根據引數類建立了一個int陣列。所以可以換成方法引用

        method(int[]::new); //拿到引數後,直接根據引數建立了一個int陣列

    }

    //引數傳遞函式式介面

    public static void method(ArrayBuilder arrayBuilder) {

        int[] arr = arrayBuilder.createArray(10);

        System.out.println(Arrays.toString(arr));

    }

}

public interface PersonBuilder {

    //給我姓名和年齡,那麼我就可以根據這個姓名和年齡給你一個Person物件。

    Person createPerson(String name, int age);

}

public interface ArrayBuilder {

    //給我長度,我就可以給你對應長度的int陣列

    int[] createArray(int len);

}

package cn.itcast.demo10;

public class Person {

    private String name;

    private int age;

    @Override

    public String toString() {

        return "Person{" +

                "name='" + name + '\'' +

                ", age=" + age +

                '}';

    }

    public Person() {

    }

    public Person(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;

    }

}

六、Java提供的函式式介面

在jdk8之後,多了一個java.util.function包。裡面提供了大量的函式式介面。

    其中有一個介面,叫做Supplier,可以看成一個生產者,裡面的get方法,可以獲取一個物件

    Supplier<T>  泛型表示要獲取的物件的資料型別。

    抽象方法:

        T get() 獲取一個物件

public class Demo01Supplier {

    public static void main(String[] args) {

        //呼叫method方法。引數是一個函式式介面,可以傳遞Lambda表示式

        method(() -> "hello");

    }

    //定義一個method方法,把函式式介面Supplier當做方法的引數

    public static void method(Supplier<String> supplier) {

        String str = supplier.get();

        System.out.println(str);

    }

}

/*

    使用 Supplier 介面作為方法引數型別,通過Lambda表示式求出int陣列中的最大值。提示:介面的泛型請使用

    java.lang.Integer 類。

*/

public class Demo02SupplierTest {

    public static void main(String[] args) {

        //定義一個int陣列

        int[] arr = {5, 10, -20, 3, 8};

        method(() -> {

            //在Lambda表示式大括號中,求出陣列的最大值,然後返回即可

            //定義參照物max用於表示每次比較的最大值。 最開始可以把陣列中索引為0的元素當成最大的賦值給這個變數

            int max = arr[0];

            //拿陣列中其他的每一個元素和這個max進行比較,如果比max大,那麼就把這個值賦值給max

            for(int i = 1; i < arr.length; i++) {

                if (arr[i] > max) {

                    max = arr[i];

                }

            }

            //求出結果之後,返回最終結果

            return max;

        });

    }

    public static void method(Supplier<Integer> supplier) {

        //呼叫Supplier的get方法獲取一個結果

        Integer result = supplier.get();

        System.out.println("result:" + result);

    }

}

2、 在java中,還有一個函式式介面叫做Consumer,可以看成一個消費者,用來消費一個數據(使用這個資料去做一些事情)。

    Consumer<T> 泛型T,表示要消費資料的資料型別。

    抽象方法:

        void accept(T t): 表示要對引數t進行消費。(拿到引數t之後去做一些事情)

public class Demo03Consumer {

    public static void main(String[] args) {

        //呼叫method方法, 傳遞一個Lambda表示式

        //method(s -> System.out.println(s));

        //方法引用的寫法

        method(System.out::println);

    }

    //定義方法,把函式式介面Consumer當做引數

    public static void method(Consumer<String> consumer) {

        consumer.accept("hello");

    }

}

/*

    在函式式介面Consumer中有一個預設方法,叫做andThen,可以將兩個Consumer合併(拼接)成一個Consumer物件

    default Consumer andThen(Consumer after): 將兩個Consumer進行合併。比如a.andThen(b)。 是對a和b進行合併。並且有先後順序是先a後b

*/

public class Demo04Consumer {

    public static void main(String[] args) {

        method(s -> System.out.println(s.toUpperCase()),

                s -> System.out.println(s.toLowerCase()));

    }

    /*

        定義方法,要兩個Consumer

        第一個Consumer用來將Hello全部轉成大寫並列印

        第二個Consumer用來將Hello全部轉成小寫並列印

     */

    public static void method(Consumer<String> one, Consumer<String> two) {

        //分別消費處理Hello

        //one.accept("Hello");

        //two.accept("Hello");

        //將one的內容和two的內容合併

        //合併之後的three裡面包含的one和two的內容, 先one後two。

        //Consumer<String> three = one.andThen(two);

        //呼叫three的accept方法,處理Hello

        //three.accept("Hello"); //因為three中包含了one和two的內容,所以這句話相當於 one.accept("Hello")  two.accept("Hello")

        //將one和two合併之後直接呼叫accept方法處理這個字串

        one.andThen(two).accept("Hello"); //先通過one呼叫accept,再通過two呼叫accept

    }

}

/*

    下面的字串陣列當中存有多條資訊,請按照格式“ 姓名:XX。性別:XX。 ”的格式將資訊打印出來。要求將列印姓

    名的動作作為第一個 Consumer 介面的Lambda例項,將列印性別的動作作為第二個 Consumer 介面的Lambda實

    例,將兩個 Consumer 介面按照順序“拼接”到一起

    列印陣列中的內容,要使用兩個Consumer進行列印, 第一個Consumer列印姓名,第二個Consumer列印性別.

    還要將這兩個Consumer合併成一個Consumer

    列印內容

        姓名:迪麗熱巴。 性別:女

        姓名:古力娜扎。 性別:女

        姓名:馬爾扎哈。 性別:男

*/

public class Demo05ConsumerTest {

    public static void main(String[] args) {

        String[] array = { "迪麗熱巴,女", "古力娜扎,女", "馬爾扎哈,男" };

        //呼叫method方法,進行處理

        method(str -> System.out.print("姓名:" + str.split(",")[0]),

                str -> System.out.println("。 性別:" + str.split(",")[1]),

                 array);

    }

    /*

        引數one用來處理陣列中每個元素的姓名

        引數two用來處理陣列中每個元素的性別

        引數arr表示要處理的陣列

     */

    public static void method(Consumer<String> one, Consumer<String> two, String[] arr) {

        //遍歷陣列,拿到陣列中的每個元素

        for(String str : arr) {

            //使用one處理列印姓名

            //one.accept(str);

            //使用two處理列印性別

            //two.accept(str);

            //將one和two進行合併,然後呼叫accept方法

            one.andThen(two).accept(str);

        }

    }

}