Java函數式編程與Lambda表達式

一.Lambda表達式

1.lambda表達式初試

  • lambda表達式是返回了實現指定接口的對象實例
  • 樣例代碼
public class Test {
    public static void main(String [] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        }).start();
        // 等同於
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        };
        new Thread(runnable).start();

        // 切換成Lambda表達式的寫法如下
        new Thread(()-> System.out.println("okk")).start();
        // 等同於
        Runnable runnable1 = ()-> System.out.println("okk");
        new Thread(runnable1).start();
    }
}

2.lambda表達式的常見寫法

  • 如果接口只存在一個抽象方法則可以使用lambda表達式編寫實現內容
  • 入參的內容寫在lambda表達式的括號內,返回內容寫在箭頭的右側
  • 實例代碼
interface Interface1 {
    int doubleNum(int i);
}
public class LambdaDemo1 {
    public static void main(String[] args) {
        // 寫法1:常見寫法
        Interface1 i1 = (i) -> i*2; // 如果是一個參數可以去掉括號, Interface1 i1 = i -> i*2;
        // 寫法2:
        Interface1 i2 = (int i) -> i*2;
        // 寫法3:
        Interface1 i3 = (int i) -> {
            return i*2;
        };
    }
}

二.JDK8接口新特性

1.函數接口

  • @FunctionalInterface,表示接口是函數接口,註解修飾的函數只存在一個需要實現的抽象方法(需要實現的方法只有一個,不是隻有一個方法,還可以有默認方法)
  • 實例代碼
@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);
}

2. 默認方法

  • default關鍵字可以用來定義默認方法,在方法中給出默認實現
  • 實例代碼
@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x+y;
    }
}
public class LambdaDemo1 {
    public static void main(String[] args) {
        Interface1 interfaceImpl = i -> i*3;
        System.out.println(interfaceImpl.doubleNum(3)); // 輸出結果爲9,因爲上方實現了對應的方法
        System.out.println(interfaceImpl.add(3, 4)); // 輸出結果爲7,因爲接口中有默認方法
    }
}
  • 如何接口繼承的其他接口存在同名的默認方法,編譯器會進行提示,要求重新實現對應的默認方法內容,實例如下
@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x+y;
    }
}

@FunctionalInterface
interface Interface2 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x+y+1;
    }
}

@FunctionalInterface
interface Interface3 extends Interface1, Interface2 { // 編譯器會提示要求重新實現add方法內容
    @Override
    default int add(int x, int y) {
        return Interface1.super.add(x, y); // 相當於調用了Interface1的接口內容,當然我們也可以寫自己的實現
    }
}

public class LambdaDemo1 {
    public static void main(String[] args) {
        Interface3 interfaceImpl = i -> i*3;
        System.out.println(interfaceImpl.doubleNum(3)); // 輸出結果爲9
        System.out.println(interfaceImpl.add(3, 4)); // 輸出結果爲7
    }
}

三.JDK8中重要的函數接口

使用現成的函數接口的好處就是不用定義過多的接口,且函數接口可以支持鏈式操作

1.Function<T, K>

  • 採用Function函數接口可以指定輸入及輸出類型的函數接口
  • 實例代碼
import java.text.DecimalFormat;
import java.util.function.Function;

interface IMoneyFormat {
    String format(int i);
}

class MyMoney {
    private final int money;

    public MyMoney(int money) {
        this.money = money;
    }

//    public void printMoney(IMoneyFormat iMoneyFormat) {
//        System.out.println("我的存款: "+ iMoneyFormat.format(this.money));
//    }

    // 此時由於我們的接口只需要知道輸入是什麼,輸出是什麼,故採用Function<T,K>函數接口即可
    public void printMoney(Function<Integer, String> iMoneyFormat) {
        System.out.println("我的存款: "+ iMoneyFormat.apply(this.money));
    }
}

public class MoneyDemo {
    public static void main(String[] args) {
        MyMoney me = new MyMoney(9999);
//        IMoneyFormat iMoneyFormat = (money)-> new DecimalFormat("#,###").format(money);
//        me.printMoney(iMoneyFormat);

        // 採用函數接口的方式實現如下
        me.printMoney((money)-> new DecimalFormat("#,###").format(money));
        // 輸出: 我的存款: 9,999

        // 採用函數接口鏈式操作特性添加前綴實現如下
        Function<Integer, String > moneyFormat = (money)-> new DecimalFormat("#,###").format(money);
        me.printMoney(moneyFormat.andThen( s -> "人民幣 " + s));
        // 輸出: 我的存款: 人民幣 9,999
    }
}

2.常用函數接口

  • 常用函數接口
接口 輸入類型 返回類型 說明
Predicate T boolean 斷言
Consumer T / 消費一個數據
Function<T, R> T R 輸入T輸出R的函數
Supplier / T 提供一個數據
UnaryOperator T T 一元函數(輸出輸入類型相同)
BiFunction<T, U, R> (T, U) R 2個輸入的函數
BinaryOperator (T, T) T 二元函數(輸出輸入類型相同)
  • 對於基本數據類型的函數接口jdk也已經提供,比如Predicate<Integer>也可以寫成IntPredicate
  • 實例代碼
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import java.util.function.Predicate;

public class Test2 {
    public static void main(String[] args) {
        // 斷言函數: 用於判斷輸入是否大於0
        Predicate<Integer> predicate = i -> i > 0;
        System.out.println(predicate.test(-2)); // 輸出: false
        // 對於基本數據類型,jdk有對應數據類型,上述內容等同如下
        IntPredicate predicate1 = i -> i > 0;

        // 消費函數
        Consumer<String> customer = s -> System.out.println("輸入的內容是: " + s);
        customer.accept("巴啦啦小魔仙"); // 輸出: 輸入的內容是: 巴啦啦小魔仙

        // 二元函數
        BinaryOperator<Integer> testAdd = (a, b) -> a + b;
        System.out.println(testAdd.apply(2, 3)); // 輸出:5

    }
}

3.方法引用

  • 靜態方法的方法引用: 類名::方法名
  • 非靜態方法的方法引用
    • 對象實例::方法名
    • 類名::方法名 - 注意第一個參數是對象實例
  • 構造函數的方法引用
    • 類名::new
  • 實例代碼
import java.util.function.*;

class Dog {
    private String name = "哮天犬";

    public Dog() {
    }

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }

    public static void bark(Dog dog) {
        System.out.println(dog + "叫了");
    }

    public static void sleep(String name) {
        System.out.println(name + "默默的睡覺...");
    }

    private int food = 10;

    /**
     * 喫狗糧
     *
     * @param num
     * @return
     */
    public int eat(int num) {
        System.out.println("吃了"+ num + "斤狗糧");
        this.food -= num;
        return this.food;
    }
}

public class MethodReferenceDemo {
    public static void main(String[] args) {
        Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("使用函數接口"); // 輸出: 使用函數接口
        // 如果內部方法的參數跟參數相同,則可以變成 方法引用 的方式進行書寫
        Consumer<String> consumer1 = System.out::println;
        consumer1.accept("使用方法引用"); // 輸出: 使用方法引用

        // 靜態方法的方法引用: 使用類名::方法名
        Consumer<Dog> dogConsumer = Dog::bark;
        Dog dog = new Dog();
        dogConsumer.accept(dog); // 輸出: Dog{name='哮天犬'}叫了

        Consumer<String> simpleConsumer = Dog::sleep;
        simpleConsumer.accept("旺財"); // 輸出: 旺財默默的睡覺...

        // 非靜態方法
        // 使用對象實例來引用
        IntUnaryOperator function = dog::eat;
        System.out.println("還剩下" + function.applyAsInt(2) + "斤"); // 輸出: 吃了2斤狗糧 還剩下8斤
        // 使用類名來引用,費靜態方法其實默認第一個參數是當前實例this,故直接給定實例爲入參即可
        BiFunction<Dog, Integer ,Integer> biFunction = Dog::eat;
        System.out.println("還剩下"+ biFunction.apply(dog, 3)+"斤"); // 輸出: 吃了3斤狗糧 還剩下5斤

        // 構造函數的方法引用, 構造函數的輸入是指定參數, 輸出是實例對象
        // 方法名是new
        // 無參構造函數如下
        Supplier<Dog> supplier = Dog::new;
        System.out.println("創建了一個新對象: " + supplier.get()); // 輸出: 創建了一個新對象: Dog{name='哮天犬'}
        // 有參構造函數如下
        Function<String, Dog> dogFunction = Dog::new;
        System.out.println("創建了個一個來福對象: " + dogFunction.apply("來福")); // 輸出: 創建了個一個來福對象: Dog{name='來福'}
    }
}

4.類型推斷

  • 通常我們可以採用類型推斷自動匹配參數內容
  • 實例代碼
@FunctionalInterface
interface IMath1 {
    int add(int x, int y);
}

@FunctionalInterface
interface IMath2 {
    int add(int x, int y);
}

public class Test3 {
    public static void main(String[] args) {
        // 變量定義
        IMath1 lambda = (x, y) -> x+y;

        // 數組中
        IMath1[] lambdas = {(x, y) -> x+y};

        // 強轉
        Object lambda2 = (IMath1)(x,y) -> x+y;

        // 通過方法返回類型
        IMath1 createLambda = createLambda();


        // 當有二義性的時候,需要使用強轉指定對應的接口
        Test3 test3 = new Test3();
        test3.test((IMath1) (x, y) -> x+y); // 如果轉換類型則會報錯
    }

    // 當有重載方法時,無法直接匹配
    public void test(IMath1 iMath1) {

    }

    public void test(IMath2 iMath2) {

    }

    static IMath1 createLambda() {
        return ((x, y) -> x+y);
    }
}

5.級聯表達式及柯里化

  • 柯里化:把多個參數的函數轉換爲只有一個參數的函數
  • 柯里化的目的:函數標準化
  • 高階函數:返回函數的函數
  • 實例代碼
import java.util.function.Function;

public class Test4 {
    public static void main(String[] args) {
        // 實現了x+y的級聯表達式
        Function<Integer, Function<Integer, Integer>> fun = x -> y -> x+y;
        System.out.println(fun.apply(2).apply(3)); // 輸出5

        Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = x -> y -> z -> x+y+z;
        System.out.println(fun2.apply(1).apply(2).apply(3));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章