Java系列筆記第八章:函數式編程

第八章 函數式編程

1. 函數式接口簡介

1.1 概念

有且只有一個抽象方法的接口,叫函數式接口。

因爲Java中函數式編程的體現就是Lambda,所以函數式接口就是可以用於lambda使用的接口。

接口中只能有一個抽象方法,可以有其他類型的方法,比如普通私有方法、靜態私有方法、默認方法等等。

語法糖:

指的是形式上使用更加簡便,但是底層原理不變的代碼語法。比如for-each寫起來簡潔,底層其實還是迭代器。

從應用層面來講,可以說lambda是匿名內部類的語法糖,但是底層原理並不同。

匿名內部類會在編譯時生成一個class文件,而lambda表達式不會,所以會降低開銷。

1.2 格式

函數式接口格式:

package FunctionInterface;

public interface MyInterface {
    void method();
}

1.3 @FunctionalInterface註解

加上這個註解後,接口就強制只能有一個抽象方法。如果多寫了一個抽象方法或者沒抽象方法,編譯會失敗。

package FunctionInterface;

@FunctionalInterface
public interface MyInterface {
    void method();
}

1.4 lambda 的小例子

package FunctionInterface;

public class Demo {
    //參數使用函數式接口
    public static void fun(MyInterface mi) {
        mi.method();
    }

    public static void main(String[] args) {
        //調用fun方法,可以傳遞接口的實現類方法或者匿名內部類。
        fun(new MyInterface() {
            @Override
            public void method() {
                System.out.println("匿名內部類重寫接口的抽象方法。");
            }
        });

        //方法的參數是一個函數式接口,所以可以傳遞進去lambda表達式
        fun(
                () -> System.out.println("lambda表達式重寫接口的抽象方法。")
        );
    }
}

//===============輸出===============//
匿名內部類重寫接口的抽象方法。
lambda表達式重寫接口的抽象方法。

2. 函數式編程

2.1 lambda的延遲執行

有的場景代碼執行完畢後,結果不一定會被使用,從而造成性能浪費。而lambda是延遲執行你的,所以可以提升性能。

2.1.1 性能浪費的日誌案例

日誌記錄代碼如下:

package FunctionInterface.logDemo;

public class Logger {
    public static void showLog(int level, String msg) {
        if (level == 1) {
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        int level = 2;
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        showLog(level, s1 + s2 + s3);
    }
}

代碼的問題:
代碼中,如果level不是1,字符串的拼接就是無用的,會浪費性能。

使用lambda來優化:

lambda的特點是延遲加載,所以先定義一個函數式接口。

lambda僅僅是傳參數進去,如果level不是1,就不會調用接口去拼接字符串。

package FunctionInterface.logDemo;

public class Logger {
    //傳遞函數式接口
    public static void showLog(int level, LogBuilderInterface logBuilderInterface) {
        //判斷日誌等級
        if (level == 1) {
            System.out.println(logBuilderInterface.buildMsg());
        }
    }

    public static void main(String[] args) {
        int level = 1;
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        showLog(level,
                () -> s1 + s2 + s3
        );
    }
}

2.2 函數式接口的使用

2.2.1作爲方法的參數。

java.lang.Runnable是一個函數式接口,假設有一個startThread方法使用Runnable接口作爲參數,就可以使用lambda進行傳參,效果等同與new Thread(//lambda表達式).start();

package FunctionInterface.InterAsParams;

public class Demo {
    public static void startThread(Runnable runnable) {
        new Thread(runnable).start();
    }

    public static void main(String[] args) {

        //使用Thread的構造方法啓動線程。
        new Thread(
                () -> System.out.println(Thread.currentThread().getName() + "線程啓動!")
        ).start();

        //startThread的參數是一個接口,可以使用這個接口的匿名內部類。
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "線程啓動!");
            }
        });
        //Runnable接口是函數式接口,作爲參數,使用lambda表達式啓動線程。
        startThread(
                () -> System.out.println(Thread.currentThread().getName() + "線程啓動!")
        );
    }
}


//===============輸出===============//
Thread-1線程啓動!
Thread-2線程啓動!
Thread-0線程啓動!

2.2.2 作爲方法的返回值

最常見的應用就是比較器java.util.Comparator

如果一個方法的返回值類型是一個接口,那麼就可以返回一個lambda表達式。

package FunctionInterface.InterAsReturn;

import java.util.Comparator;

public class Demo {
    public static Comparator<Integer> CompareInt(){
        return ((o1, o2) -> o1-o2);
    }
}

3. 常用的函數式接口

java.util.function

3.1 Supplier接口

被稱爲生產型接口,指定的泛型是什麼,接口的get方法就生產什麼類型的數據。

package FunctionInterface;

import java.util.function.Supplier;

public class SupplierDemo {
    public static String getMsg(Supplier<String> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        System.out.println(getMsg(()->"a"+"b"));
    }
}

應用:求數組最大值。

package FunctionInterface;

import java.util.function.Supplier;

public class SupplierDemo {
    public static Integer getMax(Supplier<Integer> sup) {
        return sup.get();
    }

    public static void main(String[] args) {
        int[] ints = {1, 2, 3, 4, 5};
        System.out.println(getMax(
                () -> {
                    int max = ints[0];
                    for (int i : ints) {
                        max = Math.max(i, max);
                    }
                    return max;
                }));
    }
}

3.2 Consumer接口

消費一個數據,類型由泛型決定。

void accept(T t)方法。

具體怎麼使用,需要自定義。

package FunctionInterface;

import java.util.function.Consumer;

public class ConsumerDemo {
    public static void fun(String str, Consumer<String> con) {
        con.accept(str);
    }

    public static void main(String[] args) {
        String s = "abc";
        fun(s,
                (str) -> System.out.println("傳遞進來的字符串是:" + str)
        );
    }
}

消費者接口的另一種寫法:方法引用

fun(s, System.out::println);


Consumer接口有一個默認方法:andThen

需要兩個Consumer接口,可以把兩個Consumer接口組合在一起。

誰寫在前面誰先消費。

例子:定義一個方法,傳進去一個字符串和兩個 Consumer接口。

  • 要麼調用兩次accept方法。

  • 要麼使用andThen方法。

package FunctionInterface.logDemo;

import java.util.function.Consumer;

public class ConsumerAndThenDemo {
    public static void fun(String str, Consumer<String> consumer1, Consumer<String> consumer2) {
//        consumer1.accept(str);
//        consumer2.accept(str);
        consumer1.andThen(consumer2).accept(str);
    }

    public static void main(String[] args) {
        String s = "Hello World!";
        fun(s,
                (str) -> System.out.println(str.toLowerCase()),
                (str) -> System.out.println(str.toUpperCase())
        );
    }
}

練習:格式化打印信息。

package FunctionInterface.logDemo;

import java.util.ArrayList;
import java.util.function.Consumer;

public class ConsumerAndThenDemo {
    public static void printInfo(Consumer<String> c1, Consumer<String> c2, String[] array) {
        for (String str : array) {
            c1.andThen(c2).accept(str);
        }
    }

    public static void main(String[] args) {
        String[] strings = {"張三,22", "李四,21"};
        printInfo(
                (s -> System.out.println("姓名:" + s.split(",")[0])),
                (s -> System.out.println("年齡:" + s.split(",")[1])),
                strings
        );
    }

}

//===============輸出===============//
姓名:張三
年齡:22
姓名:李四
年齡:21

3.3 Predicate接口

3.3.1 接口的使用

對某種類型的數據進行判斷,返回一個boolean值。boolean test(T t)

package FunctionInterface;

import java.util.function.Predicate;

public class PredicateDemo {
    public static boolean fun(String s, Predicate<String> predicate) {
        return predicate.test(s);
    }

    public static void main(String[] args) {
        String s = "abc";
        boolean b = fun(s, 
                (o) -> o.contains("A")
        );
        System.out.println("字符串中含有A嗎?:" + b);
    }
}

3.3.2 接口中的三個默認方法

  1. and 方法。

傳入兩個Predicate接口。

package FunctionInterface.PredicateDemo;

import java.util.function.Predicate;

public class PredicateAndDemo {
    public static boolean check(String str, Predicate<String> p1, Predicate<String> p2) {
        return p1.and(p2).test(str);
    }

    public static void main(String[] args) {
        String s = "Abc";
        boolean b = check(s,
                (o -> s.contains("A")),
                (o -> s.length() > 5)
        );
        System.out.println("字符串中既有A,長度又大於5嗎?:" + b);
    }
}
  1. or 方法。

return p1.or(p2).test(str);

  1. negate 非操作
public static boolean check(String str, Predicate<String> p) {
    return p.negate().test(str);
}

3.4 Function接口

轉換類型的接口。把一個類型的數據轉換爲另一個類型。

Function<T, R>,把T類型轉換爲R類型。

package FunctionInterface.FunctionDemo;

import java.util.function.Function;

public class Demo {
    public static void fun(String s, Function<String, Integer> fun) {
        System.out.println(fun.apply(s));
    }

    public static void main(String[] args) {
        String s = "12";
        fun(s,
                (str) -> Integer.parseInt(str)
        );
        
        //方法引用
        fun(s,
                Integer::parseInt
        );
    }
}

Function接口的默認方法:andThen。用來進行組合操作。

示例:將傳進來的字符串轉成數字,加上10,再轉爲字符串返回。

package FunctionInterface.FunctionDemo;

import java.util.function.Function;

public class andThenDemo {
    public static String fun(String str, Function<String, Integer> f1, Function<Integer, String> f2) {
        return f1.andThen(f2).apply(str);
    }

    public static void main(String[] args) {
        String s1 = "123";
        String s2 = fun(s1,
                (s -> Integer.parseInt(s) + 10),
                (i -> String.valueOf(i))
        );
        System.out.println(s2);
    }
}

(i -> String.valueOf(i))這行代碼可以使用方法引用改寫爲:
(String::valueOf)

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