溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中的函數(shù)式編程怎么使用

發(fā)布時(shí)間:2023-05-06 11:22:34 來(lái)源:億速云 閱讀:75 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“Java中的函數(shù)式編程怎么使用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Java中的函數(shù)式編程怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。

    概述

    背景

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

    函數(shù)式的價(jià)值

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

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

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

    新舊對(duì)比

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

    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)建默認(rèn)的 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 表達(dá)式,實(shí)例化 interface
                    Unrelated::twice // [4] 基于 方法引用,實(shí)例化 interface
            };
            Strategize s = new Strategize("Hello there");
            s.communicate();
            for(Strategy newStrategy : strategies) {
                s.changeStrategy(newStrategy); // [5] 使用默認(rèn)的 Soft 策略
                s.communicate(); // [6] 每次調(diào)用 communicate() 都會(huì)產(chǎn)生不同的行為
            }
        }
    }

    輸出結(jié)果:

    hello there?
    HELLO THERE!
    Hello
    Hello there Hello there

    Lambda 表達(dá)式

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

    • Lambda 表達(dá)式產(chǎn)生函數(shù),而不是類

    • Lambda 語(yǔ)法盡可能少,這正是為了使 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] 一個(gè)參數(shù)時(shí),可以不需要擴(kuò)展 (), 但這是一個(gè)特例
        static Body bod2 = (h) -> h + " More details"; // [2] 正常情況下的使用方式
        static Description desc = () -> "Short info"; // [3] 沒(méi)有參數(shù)的情況下的使用方式
        static Multi mult = (h, n) -> h + n; // [4] 多參數(shù)情況下的使用方式
    
        static Description moreLines = () -> { 
            // [5] 多行代碼情況下使用 `{}` + `return` 關(guān)鍵字
            // (在單行的 Lambda 表達(dá)式中 `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 表達(dá)式通常比匿名內(nèi)部類產(chǎn)生更易讀的代碼,因此我們將盡可能使用它們。

    方法引用

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

    使用示例:

    interface Callable { // [1] 單一方法的接口(重要)
        void call(String s);
    }
    
    class Describe {
        void show(String msg) { // [2] 符合 Callable 接口的 call() 方法實(shí)現(xiàn)
            System.out.println(msg);
        }
    }
    
    public class MethodReferences {
        static void hello(String name) { // [3] 也符合 call() 方法實(shí)現(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] 通過(guò)方法引用創(chuàng)建 Callable 的接口實(shí)現(xiàn)
            c.call("call()"); // [7] 通過(guò)該實(shí)例 call() 方法調(diào)用 show() 方法
    
            c = MethodReferences::hello; // [8] 靜態(tài)方法的方法引用
            c.call("Bob");
    
            c = new Description("valuable")::help; // [9] 實(shí)例化對(duì)象的方法引用
            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();		// 通過(guò) 方法引用創(chuàng)建 Runnable 實(shí)現(xiàn)的引用
        }
    }

    輸出結(jié)果:

    Anonymous
    lambda
    Go::go()

    未綁定的方法引用

    使用未綁定的引用時(shí),需要先提供對(duì)象:

    // 未綁定的方法引用是指沒(méi)有關(guān)聯(lián)對(duì)象的普通方法
    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] 你不能在沒(méi)有 X 對(duì)象參數(shù)的前提下調(diào)用 f(),因?yàn)樗?nbsp;X 的方法
            TransformX sp = X::f;       // [2] 你可以首個(gè)參數(shù)是 X 對(duì)象參數(shù)的前提下調(diào)用 f(),使用未綁定的引用,函數(shù)式的方法不再與方法引用的簽名完全相同
            X x = new X();
            System.out.println(sp.transform(x));      // [3] 傳入 x 對(duì)象,調(diào)用 x.f() 方法
            System.out.println(x.f());      // 同等效果
        }
    }

    輸出結(jié)果:

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

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

    package com.github.xiao2shiqi.lambda;
    
    // 未綁定的方法與多參數(shù)的結(jié)合運(yùn)用
    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ù)的引用,然后通過(guò)引用構(gòu)建對(duì)象

    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) {
            // 通過(guò) ::new 關(guān)鍵字賦值給不同的接口,然后通過(guò) make() 構(gòu)建不同的實(shí)例
            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)建一個(gè)函數(shù)式接口的實(shí)例

    • 方法引用實(shí)際上是一種簡(jiǎn)化 Lambda 表達(dá)式的語(yǔ)法糖,它提供了一種更簡(jiǎn)潔的方式來(lái)創(chuàng)建一個(gè)函數(shù)式接口的實(shí)現(xiàn)

    • 在代碼中使用方法引用時(shí),實(shí)際上是在創(chuàng)建一個(gè)匿名實(shí)現(xiàn)類,引用方法實(shí)現(xiàn)并且覆蓋了接口的抽象方法

    • 方法引用大多用于創(chuàng)建函數(shù)式接口的實(shí)現(xiàn)

    函數(shù)式接口

    • Lambda 包含類型推導(dǎo)

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

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

    // 使用 @FunctionalInterface 注解強(qiáng)制執(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 沒(méi)有實(shí)現(xiàn) Functional 接口,所以不能直接賦值
    //        Functional fac = fa;      // Incompatible ?
    
            // 但可以通過(guò) 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ù)式接口的基本使用基本準(zhǔn)測(cè),如下

    • 只處理對(duì)象而非基本類型,名稱則為 Function,Consumer,Predicate 等,參數(shù)通過(guò)泛型添加

    • 如果接收的參數(shù)是基本類型,則由名稱的第一部分表示,如 LongConsumer, DoubleFunction,IntPredicate 等

    • 如果返回值為基本類型,則用 To 表示,如 ToLongFunction 和 IntToLongFunction

    • 如果返回值類型與參數(shù)類型一致,則是一個(gè)運(yùn)算符

    • 如果接收兩個(gè)參數(shù)且返回值為布爾值,則是一個(gè)謂詞(Predicate)

    • 如果接收的兩個(gè)參數(shù)類型不同,則名稱中有一個(gè) Bi

    基本類型

    下面枚舉了基于 Lambda 表達(dá)式的所有不同 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ù)獲得對(duì)象的函數(shù)表達(dá)式
        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ù)對(duì)象類型參數(shù),獲得基本數(shù)據(jù)類型返回值的函數(shù)表達(dá)式
        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>接受一個(gè)參數(shù),返回一個(gè)結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成輸出結(jié)果,如數(shù)據(jù)轉(zhuǎn)換或映射操作R apply(T t)
    IntFunction接受一個(gè) int 參數(shù),返回一個(gè)結(jié)果將 int 值轉(zhuǎn)換成輸出結(jié)果R apply(int value)
    LongFunction接受一個(gè) long 參數(shù),返回一個(gè)結(jié)果將 long 值轉(zhuǎn)換成輸出結(jié)果R apply(long value)
    DoubleFunction接受一個(gè) double 參數(shù),返回一個(gè)結(jié)果將 double 值轉(zhuǎn)換成輸出結(jié)果R apply(double value)
    ToIntFunction接受一個(gè)參數(shù),返回一個(gè) int 結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成 int 輸出結(jié)果int applyAsInt(T value)
    ToLongFunction接受一個(gè)參數(shù),返回一個(gè) long 結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成 long 輸出結(jié)果long applyAsLong(T value)
    ToDoubleFunction接受一個(gè)參數(shù),返回一個(gè) double 結(jié)果將輸入?yún)?shù)轉(zhuǎn)換成 double 輸出結(jié)果double applyAsDouble(T value)
    IntToLongFunction接受一個(gè) int 參數(shù),返回一個(gè) long 結(jié)果將 int 值轉(zhuǎn)換成 long 輸出結(jié)果long applyAsLong(int value)
    IntToDoubleFunction接受一個(gè) int 參數(shù),返回一個(gè) double 結(jié)果將 int 值轉(zhuǎn)換成 double 輸出結(jié)果double applyAsDouble(int value)
    LongToIntFunction接受一個(gè) long 參數(shù),返回一個(gè) int 結(jié)果將 long 值轉(zhuǎn)換成 int 輸出結(jié)果int applyAsInt(long value)
    LongToDoubleFunction接受一個(gè) long 參數(shù),返回一個(gè) double 結(jié)果將 long 值轉(zhuǎn)換成 double 輸出結(jié)果double applyAsDouble(long value)
    DoubleToIntFunction接受一個(gè) double 參數(shù),返回一個(gè) int 結(jié)果將 double 值轉(zhuǎn)換成 int 輸出結(jié)果int applyAsInt(double value)
    DoubleToLongFunction接受一個(gè) double 參數(shù),返回一個(gè) long 結(jié)果將 double 值轉(zhuǎn)換成 long 輸出結(jié)果long applyAsLong(double value)

    非基本類型

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

    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ù)接口時(shí),名稱無(wú)關(guān)緊要——只要參數(shù)類型和返回類型相同。Java 會(huì)將你的方法映射到接口方法。
            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) {
            // 無(wú)參數(shù),返回一個(gè)結(jié)果
            Supplier<AA> s = ClassFunctionals::f1;
            s.get();
            // 比較兩個(gè)對(duì)象,用于排序和比較操作
            Comparator<AA> c = ClassFunctionals::f2;
            c.compare(new AA(), new AA());
            // 執(zhí)行操作,通常是副作用操作,不需要返回結(jié)果
            Consumer<AA> cons = ClassFunctionals::f3;
            cons.accept(new AA());
            // 執(zhí)行操作,通常是副作用操作,不需要返回結(jié)果,接受兩個(gè)參數(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());
            // 將兩個(gè)輸入?yún)?shù)轉(zhuǎn)換成輸出結(jié)果,如數(shù)據(jù)轉(zhuǎn)換或映射操作
            BiFunction<AA, BB, CC> bif = ClassFunctionals::f6;
            cc = bif.apply(new AA(), new BB());
            // 接受一個(gè)參數(shù),返回 boolean 值: 測(cè)試參數(shù)是否滿足特定條件
            Predicate<AA> p = ClassFunctionals::f7;
            boolean result = p.test(new AA());
            // 接受兩個(gè)參數(shù),返回 boolean 值,測(cè)試兩個(gè)參數(shù)是否滿足特定條件
            BiPredicate<AA, BB> bip = ClassFunctionals::f8;
            result = bip.test(new AA(), new BB());
            // 接受一個(gè)參數(shù),返回一個(gè)相同類型的結(jié)果,對(duì)輸入執(zhí)行單一操作并返回相同類型的結(jié)果,是 Function 的特殊情況
            UnaryOperator<AA> uo = ClassFunctionals::f9;
            AA aa = uo.apply(new AA());
            // 接受兩個(gè)相同類型的參數(shù),返回一個(gè)相同類型的結(jié)果,將兩個(gè)相同類型的值組合成一個(gè)新值,是 BiFunction 的特殊情況
            BinaryOperator<AA> bo = ClassFunctionals::f10;
            aa = bo.apply(new AA(), new AA());
        }
    }

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

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

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

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

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

    驗(yàn)證如下:

    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 表達(dá)式
            TriFunction<Integer, Long, Double, Integer> tf2 = (i, l, d) -> 12;
        }
    }

    高階函數(shù)

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

    • 接收一個(gè)或多個(gè)函數(shù)作為參數(shù)

    • 返回一個(gè)函數(shù)作為結(jié)果

    先來(lái)看看一個(gè)函數(shù)如何返回一個(gè)函數(shù):

    import java.util.function.Function;
    
    interface FuncSS extends Function<String, String> {}        // [1] 使用繼承,輕松創(chuàng)建屬于自己的函數(shù)式接口
    
    public class ProduceFunction {
        // produce() 是一個(gè)高階函數(shù):既函數(shù)的消費(fèi)者,產(chǎn)生函數(shù)的函數(shù)
        static FuncSS produce() {
            return s -> s.toLowerCase();    // [2] 使用 Lambda 表達(dá)式,可以輕松地在方法中創(chuàng)建和返回一個(gè)函數(shù)
        }
    
        public static void main(String[] args) {
            FuncSS funcSS = produce();
            System.out.println(funcSS.apply("YELLING"));
        }
    }

    然后再看看,如何接收一個(gè)函數(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ù)使代碼更加簡(jiǎn)潔、靈活和可重用,常見(jiàn)于 Stream 流式編程中

    閉包

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

    讓我們通過(guò)一個(gè)例子來(lái)理解 Java 中的閉包:

    public class ClosureExample {
        public static void main(String[] args) {
            int a = 10;
            int b = 20;
    
            // 這是一個(gè)閉包,因?yàn)樗东@了外部作用域中的變量 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(即在實(shí)際使用過(guò)程中保持不變)。這是為了防止在多線程環(huán)境中引起不可預(yù)測(cè)的行為和數(shù)據(jù)不一致。

    函數(shù)組合

    函數(shù)組合(Function Composition)意為 “多個(gè)函數(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(),
        // 重點(diǎn):使用函數(shù)組合將多個(gè)函數(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 的邏輯運(yùn)算演示代碼:

    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);    // 使用謂詞組合將多個(gè)謂詞組合在一起,negate 是取反,and 是與,or 是或
    
        public static void main(String[] args) {
            Stream.of("bar", "foobar", "foobaz", "fongopuckey")
                    .filter(p4)
                    .forEach(System.out::println);
        }
    }

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

    • negate():取反值,內(nèi)容不包含 bar

    • and(p2):長(zhǎng)度小于 5

    • or(p3):或者包含 f3

    輸出結(jié)果:

    foobar
    foobaz

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

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

    柯里化

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

    讓我們通過(guò)一個(gè)簡(jiǎn)單的 Java 示例來(lái)理解柯里化:

    public class CurryingAndPartials {
        static String uncurried(String a, String b) {
            return a + b;
        }
    
        public static void main(String[] args) {
            // 柯里化的函數(shù),它是一個(gè)接受多參數(shù)的函數(shù)
            Function<String, Function<String, String>> sum = a -> b -> a + b;
            System.out.println(uncurried("Hi ", "Ho"));
    
            // 通過(guò)鏈?zhǔn)秸{(diào)用逐個(gè)傳遞參數(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

    接下來(lái)我們添加層級(jí)來(lái)柯里化一個(gè)三參數(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;
    
            // 逐個(gè)傳遞參數(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í)候,注意選擇合適的函數(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

    讀到這里,這篇“Java中的函數(shù)式編程怎么使用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過(guò)才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI