中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中的函數式編程怎么使用

發布時間:2023-05-06 11:22:34 來源:億速云 閱讀:82 作者:iii 欄目:開發技術

本文小編為大家詳細介紹“Java中的函數式編程怎么使用”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Java中的函數式編程怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

    概述

    背景

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

    函數式的價值

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

    函數式語言已經產生了優雅的語法,這些語法對于非函數式語言也適用。 例如:如今 Python,Java 8 都在吸收 FP 的思想,并且將其融入其中,你也可以這樣想:

    OO(object oriented,面向對象)是抽象數據,FP(functional programming,函數 式編程)是抽象行為。

    新舊對比

    用傳統形式和 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] 構建默認的 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 以前的匿名內部類
                        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] 每次調用 communicate() 都會產生不同的行為
            }
        }
    }

    輸出結果:

    hello there?
    HELLO THERE!
    Hello
    Hello there Hello there

    Lambda 表達式

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

    • Lambda 表達式產生函數,而不是類

    • 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] 一個參數時,可以不需要擴展 (), 但這是一個特例
        static Body bod2 = (h) -> h + " More details"; // [2] 正常情況下的使用方式
        static Description desc = () -> "Short info"; // [3] 沒有參數的情況下的使用方式
        static Multi mult = (h, n) -> h + n; // [4] 多參數情況下的使用方式
    
        static Description moreLines = () -> { 
            // [5] 多行代碼情況下使用 `{}` + `return` 關鍵字
            // (在單行的 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());
        }
    }

    輸出結果:

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

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

    方法引用

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

    使用示例:

    interface Callable { // [1] 單一方法的接口(重要)
        void call(String s);
    }
    
    class Describe {
        void show(String msg) { // [2] 符合 Callable 接口的 call() 方法實現
            System.out.println(msg);
        }
    }
    
    public class MethodReferences {
        static void hello(String name) { // [3] 也符合 call() 方法實現
            System.out.println("Hello, " + name);
        }
    
        static class Description {
            String about;
    
            Description(String desc) {
                about = desc;
            }
    
            void help(String msg) { // [4] 靜態類的非靜態方法
                System.out.println(about + " " + msg);
            }
        }
    
        static class Helper {
            static void assist(String msg) { // [5] 靜態類的靜態方法,符合 call() 方法
                System.out.println(msg);
            }
        }
    
        public static void main(String[] args) {
            Describe d = new Describe();
            Callable c = d::show; // [6] 通過方法引用創建 Callable 的接口實現
            c.call("call()"); // [7] 通過該實例 call() 方法調用 show() 方法
    
            c = MethodReferences::hello; // [8] 靜態方法的方法引用
            c.call("Bob");
    
            c = new Description("valuable")::help; // [9] 實例化對象的方法引用
            c.call("information");
    
            c = Helper::assist; // [10] 靜態方法的方法引用
            c.call("Help!");
        }
    }

    輸出結果:

    call()
    Hello, Bob
    valuable information
    Help!

    Runnable 接口

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

    // 方法引用與 Runnable 接口的結合使用
    
    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();		// 通過 方法引用創建 Runnable 實現的引用
        }
    }

    輸出結果:

    Anonymous
    lambda
    Go::go()

    未綁定的方法引用

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

    // 未綁定的方法引用是指沒有關聯對象的普通方法
    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 對象參數的前提下調用 f(),因為它是 X 的方法
            TransformX sp = X::f;       // [2] 你可以首個參數是 X 對象參數的前提下調用 f(),使用未綁定的引用,函數式的方法不再與方法引用的簽名完全相同
            X x = new X();
            System.out.println(sp.transform(x));      // [3] 傳入 x 對象,調用 x.f() 方法
            System.out.println(x.f());      // 同等效果
        }
    }

    輸出結果:

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

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

    package com.github.xiao2shiqi.lambda;
    
    // 未綁定的方法與多參數的結合運用
    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');
        }
    }

    構造函數引用

    可以捕獲構造函數的引用,然后通過引用構建對象

    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 關鍵字賦值給不同的接口,然后通過 make() 構建不同的實例
            MakeNoArgs mna = Dog::new; // [1] 將構造函數的引用交給 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);
        }
    }

    總結

    • 方法引用在很大程度上可以理解為創建一個函數式接口的實例

    • 方法引用實際上是一種簡化 Lambda 表達式的語法糖,它提供了一種更簡潔的方式來創建一個函數式接口的實現

    • 在代碼中使用方法引用時,實際上是在創建一個匿名實現類,引用方法實現并且覆蓋了接口的抽象方法

    • 方法引用大多用于創建函數式接口的實現

    函數式接口

    • Lambda 包含類型推導

    • Java 8 引入 java.util.function 包,解決類型推導的問題

    通過函數表達式創建 Interface:

    // 使用 @FunctionalInterface 注解強制執行此 “函數式方法” 模式
    @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 沒有實現 Functional 接口,所以不能直接賦值
    //        Functional fac = fa;      // Incompatible ?
    
            // 但可以通過 Lambda 將函數賦值給接口 (類型需要匹配)
            Functional f = fa::goodbye;
            FunctionalNoAnn fna = fa::goodbye;
            Functional fl = a -> "Goodbye, " + a;
            FunctionalNoAnn fnal = a -> "Goodbye, " + a;
        }
    }

    以上是自己創建 函數式接口的示例。

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

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

    • 只處理對象而非基本類型,名稱則為 Function,Consumer,Predicate 等,參數通過泛型添加

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

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

    • 如果返回值類型與參數類型一致,則是一個運算符

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

    • 如果接收的兩個參數類型不同,則名稱中有一個 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 {
        // 根據不同參數獲得對象的函數表達式
        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);
        // 根據對象類型參數,獲得基本數據類型返回值的函數表達式
        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);
    
            // 基本類型的相互轉換
            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);
        }
    }

    以下是用表格整理基本類型相關的函數式接口:

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

    非基本類型

    在使用函數接口時,名稱無關緊要&mdash;&mdash;只要參數類型和返回類型相同。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());
    
            // 在使用函數接口時,名稱無關緊要——只要參數類型和返回類型相同。Java 會將你的方法映射到接口方法。
            bic = MethodConversion::someOtherName;
            bic.accept(new In1(), new In2());
        }
    }

    輸出結果:

    accept()
    someOtherName()

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

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

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

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

    多參數函數式接口

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

    // 創建處理 3 個參數的函數式接口
    @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;
        }
    }

    高階函數

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

    • 接收一個或多個函數作為參數

    • 返回一個函數作為結果

    先來看看一個函數如何返回一個函數:

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

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

    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());
        }
    }

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

    閉包

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

    讓我們通過一個例子來理解 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(即在實際使用過程中保持不變)。這是為了防止在多線程環境中引起不可預測的行為和數據不一致。

    函數組合

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

    先看 Function 函數組合示例代碼:

    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(),
        // 重點:使用函數組合將多個函數組合在一起
        // compose 是先執行參數中的函數,再執行調用者
        // andThen 是先執行調用者,再執行參數中的函數
        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(),它們的區別如下:

    • compose 是先執行參數中的函數,再執行調用者

    • andThen 是先執行調用者,再執行參數中的函數

    輸出結果:

    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 通過函數組合生成一個復雜的謂詞,最后應用在 filter() 中:

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

    • and(p2):長度小于 5

    • or(p3):或者包含 f3

    輸出結果:

    foobar
    foobaz

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

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

    柯里化

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

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

    public class CurryingAndPartials {
        static String uncurried(String a, String b) {
            return a + b;
        }
    
        public static void main(String[] args) {
            // 柯里化的函數,它是一個接受多參數的函數
            Function<String, Function<String, String>> sum = a -> b -> a + b;
            System.out.println(uncurried("Hi ", "Ho"));
    
            // 通過鏈式調用逐個傳遞參數
            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"));
        }
    }

    輸出結果:

    Hi Ho
    Hi Ho
    Hup Ho
    Hup Hey

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

    import java.util.function.Function;
    
    public class Curry3Args {
        public static void main(String[] args) {
            // 柯里化函數
            Function<String,
                    Function<String,
                            Function<String, String>>> sum = a -> b -> c -> a + b + c;
    
            // 逐個傳遞參數
            Function<String, Function<String, String>> hi = sum.apply("Hi ");
            Function<String, String> ho = hi.apply("Ho ");
            System.out.println(ho.apply("Hup"));
        }
    }

    輸出結果:

    Hi Ho Hup

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

    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));
        }
    }

    輸出結果:

    9

    讀到這里,這篇“Java中的函數式編程怎么使用”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    探索| 鄂伦春自治旗| 房产| 辽阳县| 饶平县| 于都县| 旌德县| 永宁县| 武平县| 清水河县| 万宁市| 阜城县| 大邑县| 容城县| 滨州市| 望城县| 德庆县| 天峻县| 包头市| 金坛市| 开封市| 临高县| 神木县| 林周县| 九寨沟县| 衡阳市| 万安县| 虹口区| 尚义县| 玉龙| 个旧市| 新昌县| 饶阳县| 莒南县| 塔城市| 仙游县| 陵水| 德安县| 屯门区| 双鸭山市| 阿合奇县|