欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你了解Java中的函數(shù)式編程

 更新時間:2023年04月02日 16:32:31   作者:小二十七  
函數(shù)式編程的理論基礎(chǔ)是阿隆佐·丘奇(Alonzo Church)于 1930 年代提出的 λ 演算(Lambda Calculus)。這篇文章主要為大家介紹了函數(shù)式編程的相關(guān)知識,希望對大家有所幫助

概述

背景

函數(shù)式編程的理論基礎(chǔ)是阿隆佐·丘奇(Alonzo Church)于 1930 年代提出的 λ 演算(Lambda Calculus)。λ 演算是一種形式系統(tǒng),用于研究函數(shù)定義、函數(shù)應(yīng)用和遞歸。它為計算理論和計算機科學(xué)的發(fā)展奠定了基礎(chǔ)。隨著 Haskell(1990年)和 Erlang(1986年)等新一代函數(shù)式編程語言的誕生,函數(shù)式編程開始在實際應(yīng)用中發(fā)揮作用。

函數(shù)式的價值

隨著硬件越來越便宜,程序的規(guī)模和復(fù)雜性都在呈線性的增長。這一切都讓編程工作變得困難重重。我們想方設(shè)法使代碼更加一致和易懂。我們急需一種 語法優(yōu)雅,簡潔健壯,高并發(fā),易于測試和調(diào)試 的編程方式,這一切恰恰就是 函數(shù)式編程(FP) 的意義所在。

函數(shù)式語言已經(jīng)產(chǎn)生了優(yōu)雅的語法,這些語法對于非函數(shù)式語言也適用。 例如:如今 Python,Java 8 都在吸收 FP 的思想,并且將其融入其中,你也可以這樣想:

OO(object oriented,面向?qū)ο螅┦浅橄髷?shù)據(jù),F(xiàn)P(functional programming,函數(shù) 式編程)是抽象行為。

新舊對比

用傳統(tǒng)形式和 Java 8 的方法引用、Lambda 表達式分別演示。代碼示例:

interface Strategy {
    String approach(String msg);
}

class Soft implements Strategy {
    public String approach(String msg) {
        return msg.toLowerCase() + "?";
    }
}

class Unrelated {
    static String twice(String msg) {
        return msg + " " + msg;
    }
}

public class Strategize {

    Strategy strategy;
    String msg;
    Strategize(String msg) {
        strategy = new Soft(); // [1] 構(gòu)建默認的 Soft
        this.msg = msg;
    }

    void communicate() {
        System.out.println(strategy.approach(msg));
    }

    void changeStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public static void main(String[] args) {
        Strategy[] strategies = {
                new Strategy() { // [2] Java 8 以前的匿名內(nèi)部類
                    public String approach(String msg) {
                        return msg.toUpperCase() + "!";
                    }
                },
                msg -> msg.substring(0, 5), // [3] 基于 Ldmbda 表達式,實例化 interface
                Unrelated::twice // [4] 基于 方法引用,實例化 interface
        };
        Strategize s = new Strategize("Hello there");
        s.communicate();
        for(Strategy newStrategy : strategies) {
            s.changeStrategy(newStrategy); // [5] 使用默認的 Soft 策略
            s.communicate(); // [6] 每次調(diào)用 communicate() 都會產(chǎn)生不同的行為
        }
    }
}

輸出結(jié)果:

hello there?
HELLO THERE!
Hello
Hello there Hello there

Lambda 表達式

Lambda 表達式是使用最小可能語法編寫的函數(shù)定義:(原則)

  • Lambda 表達式產(chǎn)生函數(shù),而不是類
  • Lambda 語法盡可能少,這正是為了使 Lambda 易于編寫和使用

Lambda 用法:

interface Description {
    String brief();
}

interface Body {
    String detailed(String head);
}

interface Multi {
    String twoArg(String head, Double d);
}

public class LambdaExpressions {

    static Body bod = h -> h + " No Parens!"; // [1] 一個參數(shù)時,可以不需要擴展 (), 但這是一個特例
    static Body bod2 = (h) -> h + " More details"; // [2] 正常情況下的使用方式
    static Description desc = () -> "Short info"; // [3] 沒有參數(shù)的情況下的使用方式
    static Multi mult = (h, n) -> h + n; // [4] 多參數(shù)情況下的使用方式

    static Description moreLines = () -> { 
        // [5] 多行代碼情況下使用 `{}` + `return` 關(guān)鍵字
        // (在單行的 Lambda 表達式中 `return` 是非法的)
        System.out.println("moreLines()");
        return "from moreLines()";
    };

    public static void main(String[] args) {
        System.out.println(bod.detailed("Oh!"));
        System.out.println(bod2.detailed("Hi!"));
        System.out.println(desc.brief());
        System.out.println(mult.twoArg("Pi! ", 3.14159));
        System.out.println(moreLines.brief());
    }
}

輸出結(jié)果:

Oh! No Parens!
Hi! More details
Short info
Pi! 3.14159
moreLines()
from moreLines()

總結(jié):Lambda 表達式通常比匿名內(nèi)部類產(chǎn)生更易讀的代碼,因此我們將盡可能使用它們。

方法引用

方法引用由類名或者對象名,后面跟著 :: 然后跟方法名稱,

使用示例:

interface Callable { // [1] 單一方法的接口(重要)
    void call(String s);
}

class Describe {
    void show(String msg) { // [2] 符合 Callable 接口的 call() 方法實現(xiàn)
        System.out.println(msg);
    }
}

public class MethodReferences {
    static void hello(String name) { // [3] 也符合 call() 方法實現(xiàn)
        System.out.println("Hello, " + name);
    }

    static class Description {
        String about;

        Description(String desc) {
            about = desc;
        }

        void help(String msg) { // [4] 靜態(tài)類的非靜態(tài)方法
            System.out.println(about + " " + msg);
        }
    }

    static class Helper {
        static void assist(String msg) { // [5] 靜態(tài)類的靜態(tài)方法,符合 call() 方法
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        Describe d = new Describe();
        Callable c = d::show; // [6] 通過方法引用創(chuàng)建 Callable 的接口實現(xiàn)
        c.call("call()"); // [7] 通過該實例 call() 方法調(diào)用 show() 方法

        c = MethodReferences::hello; // [8] 靜態(tài)方法的方法引用
        c.call("Bob");

        c = new Description("valuable")::help; // [9] 實例化對象的方法引用
        c.call("information");

        c = Helper::assist; // [10] 靜態(tài)方法的方法引用
        c.call("Help!");
    }
}

輸出結(jié)果:

call()
Hello, Bob
valuable information
Help!

Runnable 接口

使用 Lambda 和方法引用改變 Runnable 接口的寫法:

// 方法引用與 Runnable 接口的結(jié)合使用

class Go {
    static void go() {
        System.out.println("Go::go()");
    }
}

public class RunnableMethodReference {

    public static void main(String[] args) {

        new Thread(new Runnable() {
            public void run() {
                System.out.println("Anonymous");
            }
        }).start();

        new Thread(
                () -> System.out.println("lambda")
        ).start();

        new Thread(Go::go).start();		// 通過 方法引用創(chuàng)建 Runnable 實現(xiàn)的引用
    }
}

輸出結(jié)果:

Anonymous
lambda
Go::go()

未綁定的方法引用

使用未綁定的引用時,需要先提供對象:

// 未綁定的方法引用是指沒有關(guān)聯(lián)對象的普通方法
class X {
    String f() {
        return "X::f()";
    }
}

interface MakeString {
    String make();
}

interface TransformX {
    String transform(X x);
}

public class UnboundMethodReference {

    public static void main(String[] args) {
        // MakeString sp = X::f;       // [1] 你不能在沒有 X 對象參數(shù)的前提下調(diào)用 f(),因為它是 X 的方法
        TransformX sp = X::f;       // [2] 你可以首個參數(shù)是 X 對象參數(shù)的前提下調(diào)用 f(),使用未綁定的引用,函數(shù)式的方法不再與方法引用的簽名完全相同
        X x = new X();
        System.out.println(sp.transform(x));      // [3] 傳入 x 對象,調(diào)用 x.f() 方法
        System.out.println(x.f());      // 同等效果
    }
}

輸出結(jié)果:

X::f()
X::f()

我們通過更多示例來證明,通過未綁的方法引用和 interface 之間建立關(guān)聯(lián):

package com.github.xiao2shiqi.lambda;

// 未綁定的方法與多參數(shù)的結(jié)合運用
class This {
    void two(int i, double d) {}
    void three(int i, double d, String s) {}
    void four(int i, double d, String s, char c) {}
}
interface TwoArgs {
    void call2(This athis, int i, double d);
}
interface ThreeArgs {
    void call3(This athis, int i, double d, String s);
}
interface FourArgs {
    void call4(
            This athis, int i, double d, String s, char c);
}

public class MultiUnbound {

    public static void main(String[] args) {
        TwoArgs twoargs = This::two;
        ThreeArgs threeargs = This::three;
        FourArgs fourargs = This::four;
        This athis = new This();
        twoargs.call2(athis, 11, 3.14);
        threeargs.call3(athis, 11, 3.14, "Three");
        fourargs.call4(athis, 11, 3.14, "Four", 'Z');
    }
}

構(gòu)造函數(shù)引用

可以捕獲構(gòu)造函數(shù)的引用,然后通過引用構(gòu)建對象

class Dog {
    String name;
    int age = -1; // For "unknown"
    Dog() { name = "stray"; }
    Dog(String nm) { name = nm; }
    Dog(String nm, int yrs) {
        name = nm;
        age = yrs;
    }
}

interface MakeNoArgs {
    Dog make();
}

interface Make1Arg {
    Dog make(String nm);
}

interface Make2Args {
    Dog make(String nm, int age);
}

public class CtorReference {
    public static void main(String[] args) {
        // 通過 ::new 關(guān)鍵字賦值給不同的接口,然后通過 make() 構(gòu)建不同的實例
        MakeNoArgs mna = Dog::new; // [1] 將構(gòu)造函數(shù)的引用交給 MakeNoArgs 接口
        Make1Arg m1a = Dog::new; // [2] …………
        Make2Args m2a = Dog::new; // [3] …………
        Dog dn = mna.make();
        Dog d1 = m1a.make("Comet");
        Dog d2 = m2a.make("Ralph", 4);
    }
}

總結(jié)

  • 方法引用在很大程度上可以理解為創(chuàng)建一個函數(shù)式接口的實例
  • 方法引用實際上是一種簡化 Lambda 表達式的語法糖,它提供了一種更簡潔的方式來創(chuàng)建一個函數(shù)式接口的實現(xiàn)
  • 在代碼中使用方法引用時,實際上是在創(chuàng)建一個匿名實現(xiàn)類,引用方法實現(xiàn)并且覆蓋了接口的抽象方法
  • 方法引用大多用于創(chuàng)建函數(shù)式接口的實現(xiàn)

函數(shù)式接口

  • Lambda 包含類型推導(dǎo)
  • Java 8 引入 java.util.function 包,解決類型推導(dǎo)的問題

通過函數(shù)表達式創(chuàng)建 Interface:

// 使用 @FunctionalInterface 注解強制執(zhí)行此 “函數(shù)式方法” 模式
@FunctionalInterface
interface Functional {
    String goodbye(String arg);
}

interface FunctionalNoAnn {
    String goodbye(String arg);
}

public class FunctionalAnnotation {
    // goodbye
    public String goodbye(String arg) {
        return "Goodbye, " + arg + "!";
    }

    public static void main(String[] args) {
        FunctionalAnnotation fa = new FunctionalAnnotation();

        // FunctionalAnnotation 沒有實現(xiàn) Functional 接口,所以不能直接賦值
//        Functional fac = fa;      // Incompatible ?

        // 但可以通過 Lambda 將函數(shù)賦值給接口 (類型需要匹配)
        Functional f = fa::goodbye;
        FunctionalNoAnn fna = fa::goodbye;
        Functional fl = a -> "Goodbye, " + a;
        FunctionalNoAnn fnal = a -> "Goodbye, " + a;
    }
}

以上是自己創(chuàng)建 函數(shù)式接口的示例。

但在 java.util.function 包旨在創(chuàng)建一組完整的預(yù)定義接口,使得我們一般情況下不需再定義自己的接口。

在 java.util.function 的函數(shù)式接口的基本使用基本準測,如下

  • 只處理對象而非基本類型,名稱則為 Function,Consumer,Predicate 等,參數(shù)通過泛型添加
  • 如果接收的參數(shù)是基本類型,則由名稱的第一部分表示,如 LongConsumer, DoubleFunction,IntPredicate 等
  • 如果返回值為基本類型,則用 To 表示,如 ToLongFunction 和 IntToLongFunction
  • 如果返回值類型與參數(shù)類型一致,則是一個運算符
  • 如果接收兩個參數(shù)且返回值為布爾值,則是一個謂詞(Predicate)
  • 如果接收的兩個參數(shù)類型不同,則名稱中有一個 Bi

基本類型

下面枚舉了基于 Lambda 表達式的所有不同 Function 變體的示例:

class Foo {}

class Bar {
    Foo f;
    Bar(Foo f) { this.f = f; }
}

class IBaz {
    int i;
    IBaz(int i) { this.i = i; }
}

class LBaz {
    long l;
    LBaz(long l) { this.l = l; }
}

class DBaz {
    double d;
    DBaz(double d) { this.d = d; }
}

public class FunctionVariants {
    // 根據(jù)不同參數(shù)獲得對象的函數(shù)表達式
    static Function<Foo, Bar> f1 = f -> new Bar(f);
    static IntFunction<IBaz> f2 = i -> new IBaz(i);
    static LongFunction<LBaz> f3 = l -> new LBaz(l);
    static DoubleFunction<DBaz> f4 = d -> new DBaz(d);
    // 根據(jù)對象類型參數(shù),獲得基本數(shù)據(jù)類型返回值的函數(shù)表達式
    static ToIntFunction<IBaz> f5 = ib -> ib.i;
    static ToLongFunction<LBaz> f6 = lb -> lb.l;
    static ToDoubleFunction<DBaz> f7 = db -> db.d;
    static IntToLongFunction f8 = i -> i;
    static IntToDoubleFunction f9 = i -> i;
    static LongToIntFunction f10 = l -> (int)l;
    static LongToDoubleFunction f11 = l -> l;
    static DoubleToIntFunction f12 = d -> (int)d;
    static DoubleToLongFunction f13 = d -> (long)d;

    public static void main(String[] args) {
        // apply usage examples
        Bar b = f1.apply(new Foo());
        IBaz ib = f2.apply(11);
        LBaz lb = f3.apply(11);
        DBaz db = f4.apply(11);

        // applyAs* usage examples
        int i = f5.applyAsInt(ib);
        long l = f6.applyAsLong(lb);
        double d = f7.applyAsDouble(db);

        // 基本類型的相互轉(zhuǎn)換
        long applyAsLong = f8.applyAsLong(12);
        double applyAsDouble = f9.applyAsDouble(12);
        int applyAsInt = f10.applyAsInt(12);
        double applyAsDouble1 = f11.applyAsDouble(12);
        int applyAsInt1 = f12.applyAsInt(13.0);
        long applyAsLong1 = f13.applyAsLong(13.0);
    }
}

以下是用表格整理基本類型相關(guān)的函數(shù)式接口:

函數(shù)式接口特征用途方法名
Function<T, R>接受一個參數(shù),返回一個結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成輸出結(jié)果,如數(shù)據(jù)轉(zhuǎn)換或映射操作R apply(T t)
IntFunction接受一個 int 參數(shù),返回一個結(jié)果將 int 值轉(zhuǎn)換成輸出結(jié)果R apply(int value)
LongFunction接受一個 long 參數(shù),返回一個結(jié)果將 long 值轉(zhuǎn)換成輸出結(jié)果R apply(long value)
DoubleFunction接受一個 double 參數(shù),返回一個結(jié)果將 double 值轉(zhuǎn)換成輸出結(jié)果R apply(double value)
ToIntFunction接受一個參數(shù),返回一個 int 結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成 int 輸出結(jié)果int applyAsInt(T value)
ToLongFunction接受一個參數(shù),返回一個 long 結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成 long 輸出結(jié)果long applyAsLong(T value)
ToDoubleFunction接受一個參數(shù),返回一個 double 結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成 double 輸出結(jié)果double applyAsDouble(T value)
IntToLongFunction接受一個 int 參數(shù),返回一個 long 結(jié)果將 int 值轉(zhuǎn)換成 long 輸出結(jié)果long applyAsLong(int value)
IntToDoubleFunction接受一個 int 參數(shù),返回一個 double 結(jié)果將 int 值轉(zhuǎn)換成 double 輸出結(jié)果double applyAsDouble(int value)
LongToIntFunction接受一個 long 參數(shù),返回一個 int 結(jié)果將 long 值轉(zhuǎn)換成 int 輸出結(jié)果int applyAsInt(long value)
LongToDoubleFunction接受一個 long 參數(shù),返回一個 double 結(jié)果將 long 值轉(zhuǎn)換成 double 輸出結(jié)果double applyAsDouble(long value)
DoubleToIntFunction接受一個 double 參數(shù),返回一個 int 結(jié)果將 double 值轉(zhuǎn)換成 int 輸出結(jié)果int applyAsInt(double value)
DoubleToLongFunction接受一個 double 參數(shù),返回一個 long 結(jié)果將 double 值轉(zhuǎn)換成 long 輸出結(jié)果long applyAsLong(double value)

非基本類型

在使用函數(shù)接口時,名稱無關(guān)緊要——只要參數(shù)類型和返回類型相同。Java 會將你的方法映射到接口方法。示例:

import java.util.function.BiConsumer;

class In1 {}
class In2 {}

public class MethodConversion {

    static void accept(In1 in1, In2 in2) {
        System.out.println("accept()");
    }

    static void someOtherName(In1 in1, In2 in2) {
        System.out.println("someOtherName()");
    }

    public static void main(String[] args) {
        BiConsumer<In1, In2> bic;

        bic = MethodConversion::accept;
        bic.accept(new In1(), new In2());

        // 在使用函數(shù)接口時,名稱無關(guān)緊要——只要參數(shù)類型和返回類型相同。Java 會將你的方法映射到接口方法。
        bic = MethodConversion::someOtherName;
        bic.accept(new In1(), new In2());
    }
}

輸出結(jié)果:

accept()
someOtherName()

將方法引用應(yīng)用于基于類的函數(shù)式接口(即那些不包含基本類型的函數(shù)式接口)

import java.util.Comparator;
import java.util.function.*;

class AA {}
class BB {}
class CC {}

public class ClassFunctionals {

    static AA f1() { return new AA(); }
    static int f2(AA aa1, AA aa2) { return 1; }
    static void f3 (AA aa) {}
    static void f4 (AA aa, BB bb) {}
    static CC f5 (AA aa) { return new CC(); }
    static CC f6 (AA aa, BB bb) { return new CC(); }
    static boolean f7 (AA aa) { return true; }
    static boolean f8 (AA aa, BB bb) { return true; }
    static AA f9 (AA aa) { return new AA(); }
    static AA f10 (AA aa, AA bb) { return new AA(); }

    public static void main(String[] args) {
        // 無參數(shù),返回一個結(jié)果
        Supplier<AA> s = ClassFunctionals::f1;
        s.get();
        // 比較兩個對象,用于排序和比較操作
        Comparator<AA> c = ClassFunctionals::f2;
        c.compare(new AA(), new AA());
        // 執(zhí)行操作,通常是副作用操作,不需要返回結(jié)果
        Consumer<AA> cons = ClassFunctionals::f3;
        cons.accept(new AA());
        // 執(zhí)行操作,通常是副作用操作,不需要返回結(jié)果,接受兩個參數(shù)
        BiConsumer<AA, BB> bicons = ClassFunctionals::f4;
        bicons.accept(new AA(), new BB());
        // 將輸入?yún)?shù)轉(zhuǎn)換成輸出結(jié)果,如數(shù)據(jù)轉(zhuǎn)換或映射操作
        Function<AA, CC> f = ClassFunctionals::f5;
        CC cc = f.apply(new AA());
        // 將兩個輸入?yún)?shù)轉(zhuǎn)換成輸出結(jié)果,如數(shù)據(jù)轉(zhuǎn)換或映射操作
        BiFunction<AA, BB, CC> bif = ClassFunctionals::f6;
        cc = bif.apply(new AA(), new BB());
        // 接受一個參數(shù),返回 boolean 值: 測試參數(shù)是否滿足特定條件
        Predicate<AA> p = ClassFunctionals::f7;
        boolean result = p.test(new AA());
        // 接受兩個參數(shù),返回 boolean 值,測試兩個參數(shù)是否滿足特定條件
        BiPredicate<AA, BB> bip = ClassFunctionals::f8;
        result = bip.test(new AA(), new BB());
        // 接受一個參數(shù),返回一個相同類型的結(jié)果,對輸入執(zhí)行單一操作并返回相同類型的結(jié)果,是 Function 的特殊情況
        UnaryOperator<AA> uo = ClassFunctionals::f9;
        AA aa = uo.apply(new AA());
        // 接受兩個相同類型的參數(shù),返回一個相同類型的結(jié)果,將兩個相同類型的值組合成一個新值,是 BiFunction 的特殊情況
        BinaryOperator<AA> bo = ClassFunctionals::f10;
        aa = bo.apply(new AA(), new AA());
    }
}

以下是用表格整理的非基本類型的函數(shù)式接口:

函數(shù)式接口特征用途方法名
Supplier無參數(shù),返回一個結(jié)果獲取值或?qū)嵗?,工廠模式,延遲計算T get()
Comparator接受兩個參數(shù),返回 int 值比較兩個對象,用于排序和比較操作int compare(T o1, T o2)
Consumer接受一個參數(shù),無返回值執(zhí)行操作,通常是副作用操作,不需要返回結(jié)果void accept(T t)
BiConsumer<T, U>接受兩個參數(shù),無返回值執(zhí)行操作,通常是副作用操作,不需要返回結(jié)果,接受兩個參數(shù)void accept(T t, U u)
Function<T, R>接受一個參數(shù),返回一個結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成輸出結(jié)果,如數(shù)據(jù)轉(zhuǎn)換或映射操作R apply(T t)
BiFunction<T, U, R>接受兩個參數(shù),返回一個結(jié)果將兩個輸入?yún)?shù)轉(zhuǎn)換成輸出結(jié)果,如數(shù)據(jù)轉(zhuǎn)換或映射操作R apply(T t, U u)
Predicate接受一個參數(shù),返回 boolean 值測試參數(shù)是否滿足特定條件boolean test(T t)
BiPredicate<T, U>接受兩個參數(shù),返回 boolean 值測試兩個參數(shù)是否滿足特定條件boolean test(T t, U u)
UnaryOperator接受一個參數(shù),返回一個相同類型的結(jié)果對輸入執(zhí)行單一操作并返回相同類型的結(jié)果,是 Function 的特殊情況T apply(T t)
BinaryOperator接受兩個相同類型的參數(shù),返回一個相同類型的結(jié)果將兩個相同類型的值組合成一個新值,是 BiFunction 的特殊情況T apply(T t1, T t2)

多參數(shù)函數(shù)式接口

java.util.functional 中的接口是有限的,如果需要 3 個參數(shù)函數(shù)的接口怎么辦?自己創(chuàng)建就可以了,如下:

// 創(chuàng)建處理 3 個參數(shù)的函數(shù)式接口
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
    
    R apply(T t, U u, V v);
}

驗證如下:

public class TriFunctionTest {
    static int f(int i, long l, double d) { return 99; }

    public static void main(String[] args) {
        // 方法引用
        TriFunction<Integer, Long, Double, Integer> tf1 = TriFunctionTest::f;
        // Lamdba 表達式
        TriFunction<Integer, Long, Double, Integer> tf2 = (i, l, d) -> 12;
    }
}

高階函數(shù)

高階函數(shù)(Higher-order Function)其實很好理解,并且在函數(shù)式編程中非常常見,它有以下特點:

  • 接收一個或多個函數(shù)作為參數(shù)
  • 返回一個函數(shù)作為結(jié)果

先來看看一個函數(shù)如何返回一個函數(shù):

import java.util.function.Function;

interface FuncSS extends Function<String, String> {}        // [1] 使用繼承,輕松創(chuàng)建屬于自己的函數(shù)式接口

public class ProduceFunction {
    // produce() 是一個高階函數(shù):既函數(shù)的消費者,產(chǎn)生函數(shù)的函數(shù)
    static FuncSS produce() {
        return s -> s.toLowerCase();    // [2] 使用 Lambda 表達式,可以輕松地在方法中創(chuàng)建和返回一個函數(shù)
    }

    public static void main(String[] args) {
        FuncSS funcSS = produce();
        System.out.println(funcSS.apply("YELLING"));
    }
}

然后再看看,如何接收一個函數(shù)作為函數(shù)的參數(shù):

class One {}
class Two {}

public class ConsumeFunction {
    static Two consume(Function<One, Two> onetwo) {
        return onetwo.apply(new One());
    }

    public static void main(String[] args) {
        Two two = consume(one -> new Two());
    }
}

總之,高階函數(shù)使代碼更加簡潔、靈活和可重用,常見于 Stream 流式編程中

閉包

在 Java 中,閉包通常與 lambda 表達式和匿名內(nèi)部類相關(guān)。簡單來說,閉包允許在一個函數(shù)內(nèi)部訪問和操作其外部作用域中的變量。在 Java 中的閉包實際上是一個特殊的對象,它封裝了一個函數(shù)及其相關(guān)的環(huán)境。這意味著閉包不僅僅是一個函數(shù),它還攜帶了一個執(zhí)行上下文,其中包括外部作用域中的變量。這使得閉包在訪問這些變量時可以在不同的執(zhí)行上下文中保持它們的值。

讓我們通過一個例子來理解 Java 中的閉包:

public class ClosureExample {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;

        // 這是一個閉包,因為它捕獲了外部作用域中的變量 a 和 b
        IntBinaryOperator closure = (x, y) -> x * a + y * b;

        int result = closure.applyAsInt(3, 4);
        System.out.println("Result: " + result); // 輸出 "Result: 110"
    }
}

需要注意的是,在 Java 中,閉包捕獲的外部變量必須是 final 或者是有效的 final(即在實際使用過程中保持不變)。這是為了防止在多線程環(huán)境中引起不可預(yù)測的行為和數(shù)據(jù)不一致。

函數(shù)組合

函數(shù)組合(Function Composition)意為 “多個函數(shù)組合成新函數(shù)”。它通常是函數(shù)式 編程的基本組成部分。

先看 Function 函數(shù)組合示例代碼:

import java.util.function.Function;

public class FunctionComposition {
    static Function<String, String> f1 = s -> {
        System.out.println(s);
        return s.replace('A', '_');
    },
    f2 = s -> s.substring(3),
    f3 = s -> s.toLowerCase(),
    // 重點:使用函數(shù)組合將多個函數(shù)組合在一起
    // compose 是先執(zhí)行參數(shù)中的函數(shù),再執(zhí)行調(diào)用者
    // andThen 是先執(zhí)行調(diào)用者,再執(zhí)行參數(shù)中的函數(shù)
    f4 = f1.compose(f2).andThen(f3);        

    public static void main(String[] args) {
        String s = f4.apply("GO AFTER ALL AMBULANCES");
        System.out.println(s);
    }
}

代碼示例使用了 Function 里的 compose() 和 andThen(),它們的區(qū)別如下:

  • compose 是先執(zhí)行參數(shù)中的函數(shù),再執(zhí)行調(diào)用者
  • andThen 是先執(zhí)行調(diào)用者,再執(zhí)行參數(shù)中的函數(shù)

輸出結(jié)果:

AFTER ALL AMBULANCES
_fter _ll _mbul_nces

然后,再看一段 Predicate 的邏輯運算演示代碼:

public class PredicateComposition {
    static Predicate<String>
            p1 = s -> s.contains("bar"),
            p2 = s -> s.length() < 5,
            p3 = s -> s.contains("foo"),
            p4 = p1.negate().and(p2).or(p3);    // 使用謂詞組合將多個謂詞組合在一起,negate 是取反,and 是與,or 是或

    public static void main(String[] args) {
        Stream.of("bar", "foobar", "foobaz", "fongopuckey")
                .filter(p4)
                .forEach(System.out::println);
    }
}

p4 通過函數(shù)組合生成一個復(fù)雜的謂詞,最后應(yīng)用在 filter() 中:

  • negate():取反值,內(nèi)容不包含 bar
  • and(p2):長度小于 5
  • or(p3):或者包含 f3

輸出結(jié)果:

foobar
foobaz

在 java.util.function 中常用的支持函數(shù)組合的方法,大致如下:

函數(shù)式接口方法名描述
Function<T, R>andThen用于從左到右組合兩個函數(shù),即:h(x) = g(f(x))
Function<T, R>compose用于從右到左組合兩個函數(shù),即:h(x) = f(g(x))
ConsumerandThen用于從左到右組合兩個消費者,按順序執(zhí)行兩個消費者操作
Predicateand用于組合兩個謂詞函數(shù),返回一個新的謂詞函數(shù),滿足兩個謂詞函數(shù)的條件
Predicateor用于組合兩個謂詞函數(shù),返回一個新的謂詞函數(shù),滿足其中一個謂詞函數(shù)的條件
Predicatenegate用于對謂詞函數(shù)取反,返回一個新的謂詞函數(shù),滿足相反的條件
UnaryOperatorandThen用于從左到右組合兩個一元操作符,即:h(x) = g(f(x))
UnaryOperatorcompose用于從右到左組合兩個一元操作符,即:h(x) = f(g(x))
BinaryOperatorandThen用于從左到右組合兩個二元操作符,即:h(x, y) = g(f(x, y))

柯里化

柯里化(Currying)是函數(shù)式編程中的一種技術(shù),它將一個接受多個參數(shù)的函數(shù)轉(zhuǎn)換為一系列單參數(shù)函數(shù)。

讓我們通過一個簡單的 Java 示例來理解柯里化:

public class CurryingAndPartials {
    static String uncurried(String a, String b) {
        return a + b;
    }

    public static void main(String[] args) {
        // 柯里化的函數(shù),它是一個接受多參數(shù)的函數(shù)
        Function<String, Function<String, String>> sum = a -> b -> a + b;
        System.out.println(uncurried("Hi ", "Ho"));

        // 通過鏈式調(diào)用逐個傳遞參數(shù)
        Function<String, String> hi = sum.apply("Hi ");
        System.out.println(hi.apply("Ho"));

        Function<String, String> sumHi = sum.apply("Hup ");
        System.out.println(sumHi.apply("Ho"));
        System.out.println(sumHi.apply("Hey"));
    }
}

輸出結(jié)果:

Hi Ho
Hi Ho
Hup Ho
Hup Hey

接下來我們添加層級來柯里化一個三參數(shù)函數(shù):

import java.util.function.Function;

public class Curry3Args {
    public static void main(String[] args) {
        // 柯里化函數(shù)
        Function<String,
                Function<String,
                        Function<String, String>>> sum = a -> b -> c -> a + b + c;

        // 逐個傳遞參數(shù)
        Function<String, Function<String, String>> hi = sum.apply("Hi ");
        Function<String, String> ho = hi.apply("Ho ");
        System.out.println(ho.apply("Hup"));
    }
}

輸出結(jié)果:

Hi Ho Hup

在處理基本類型的時候,注意選擇合適的函數(shù)式接口:

import java.util.function.IntFunction;
import java.util.function.IntUnaryOperator;

public class CurriedIntAdd {
    public static void main(String[] args) {
        IntFunction<IntUnaryOperator> curriedIntAdd = a -> b -> a + b;
        IntUnaryOperator add4 = curriedIntAdd.apply(4);
        System.out.println(add4.applyAsInt(5));
    }
}

輸出結(jié)果:

9

總結(jié)

Lambda 表達式和方法引用并沒有將 Java 轉(zhuǎn)換成函數(shù)式語言,而是提供了對函數(shù)式編程的支持(Java 的歷史包袱太重了),這些特性滿足了很大一部分的、羨慕 Clojure 和 Scala 這類更函數(shù)化語言的 Java 程序員。阻止了他們投奔向那些語言(或者至少讓他們在投奔之前做好準備)??傊?,Lambdas 和方法引用是 Java 8 中的巨大改進

以上就是一文帶你了解Java中的函數(shù)式編程的詳細內(nèi)容,更多關(guān)于Java函數(shù)式編程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • springboot返回值轉(zhuǎn)成JSONString的處理方式

    springboot返回值轉(zhuǎn)成JSONString的處理方式

    這篇文章主要介紹了springboot返回值轉(zhuǎn)成JSONString的處理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Java向Runnable線程傳遞參數(shù)方法實例解析

    Java向Runnable線程傳遞參數(shù)方法實例解析

    這篇文章主要介紹了Java向Runnable線程傳遞參數(shù)方法實例解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • Java之SpringCloud nocos注冊中心講解

    Java之SpringCloud nocos注冊中心講解

    這篇文章主要介紹了Java之SpringCloud nocos注冊中心講解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • dubbo整合springboot新手入門教程詳解

    dubbo整合springboot新手入門教程詳解

    這篇文章主要介紹了dubbo整合springboot新手入門詳解,當一臺計算機的程序需要調(diào)用另一臺計算機代碼的時候,就涉及遠程調(diào)用。此時dubbo就粉末登場了,需要的朋友可以參考下
    2019-07-07
  • Spring MVC---數(shù)據(jù)綁定和表單標簽詳解

    Spring MVC---數(shù)據(jù)綁定和表單標簽詳解

    本篇文章主要介紹了Spring MVC---數(shù)據(jù)綁定和表單標簽詳解,具有一定的參考價值,有興趣的可以了解一下。
    2017-01-01
  • 詳解SpringBoot配置文件啟動時動態(tài)配置參數(shù)方法

    詳解SpringBoot配置文件啟動時動態(tài)配置參數(shù)方法

    這篇文章主要介紹了詳解SpringBoot配置文件啟動時動態(tài)配置參數(shù)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • SpringBoot項目中定時器的實現(xiàn)示例

    SpringBoot項目中定時器的實現(xiàn)示例

    在Spring?Boot項目中,你可以使用Spring框架提供的@Scheduled注解來編寫定時任務(wù),本文就來介紹一下SpringBoot項目中定時器的實現(xiàn),感興趣的可以了解一下
    2023-11-11
  • java生成sm2/hutool生成公鑰私鑰代碼示例

    java生成sm2/hutool生成公鑰私鑰代碼示例

    這篇文章主要給大家介紹了關(guān)于java生成sm2/hutool生成公鑰私鑰的相關(guān)資料,Java是一種廣泛使用的編程語言,可以用來生成公鑰和私鑰文件,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-09-09
  • Java中Spock框架Mock對象的方法經(jīng)驗總結(jié)

    Java中Spock框架Mock對象的方法經(jīng)驗總結(jié)

    這篇文章主要分享了Spock框架Mock對象的方法經(jīng)驗總結(jié),下文分享一些常用項目實戰(zhàn)說明以及代碼,供大家項目中參考,也具有一的的參考價值,需要的小伙伴可以參考一下
    2022-02-02
  • springboot?vue測試列表遞歸查詢子節(jié)點下的接口功能實現(xiàn)

    springboot?vue測試列表遞歸查詢子節(jié)點下的接口功能實現(xiàn)

    這篇文章主要為大家介紹了springboot?vue測試列表遞歸查詢子節(jié)點下的接口功能實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05

最新評論