日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

概述

背景

函數(shù)式編程的理論基礎(chǔ)是阿隆佐·丘奇(Alonzo Church)于1930 年代提出的 λ 演算(Lambda Calculus)。λ 演算是一種形式系統(tǒng),用于研究函數(shù)定義、函數(shù)應(yīng)用和遞歸。它為計算理論和計算機(jī)科學(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/ target=_blank class=infotextkey>Python,JAVA 8 都在吸收 FP 的思想,并且將其融入其中,你也可以這樣想:

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

新舊對比

用傳統(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á)式,實例化 interface
                Unrelated::twice // [4] 基于 方法引用,實例化 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() 都會產(chǎn)生不同的行為
        }
    }
}

輸出結(jié)果:

hello there?
HELLO THERE!
Hello
Hello there Hello there

Lambda 表達(dá)式

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

  1. Lambda 表達(dá)式產(chǎn)生函數(shù),而不是類
  2. 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ù)時,可以不需要擴(kuò)展 (), 但這是一個特例
    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 表達(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)生更易讀的代碼,因此我們將盡可能使用它們。

方法引用

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

使用示例:

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 表達(dá)式的語法糖,它提供了一種更簡潔的方式來創(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ù)表達(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 沒有實現(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ù)式接口的基本使用基本準(zhǔn)測,如下

  1. 只處理對象而非基本類型,名稱則為 Function,Consumer,Predicate 等,參數(shù)通過泛型添加
  2. 如果接收的參數(shù)是基本類型,則由名稱的第一部分表示,如 LongConsumer, DoubleFunction,IntPredicate 等
  3. 如果返回值為基本類型,則用 To 表示,如 ToLongFunction 和 IntToLongFunction
  4. 如果返回值類型與參數(shù)類型一致,則是一個運算符
  5. 如果接收兩個參數(shù)且返回值為布爾值,則是一個謂詞(Predicate)
  6. 如果接收的兩個參數(shù)類型不同,則名稱中有一個 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ù)獲得對象的函數(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ù)對象類型參數(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>

接受一個參數(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ū)嵗S模式,延遲計算

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

高階函數(shù)

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

  1. 接收一個或多個函數(shù)作為參數(shù)
  2. 返回一個函數(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 表達(dá)式,可以輕松地在方法中創(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> .NETwo) {
        return onetwo.apply(new One());
    }

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

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

閉包

在 Java 中,閉包通常與 lambda 表達(dá)式和匿名內(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))

Consumer

andThen

用于從左到右組合兩個消費者,按順序執(zhí)行兩個消費者操作

Predicate

and

用于組合兩個謂詞函數(shù),返回一個新的謂詞函數(shù),滿足兩個謂詞函數(shù)的條件

Predicate

or

用于組合兩個謂詞函數(shù),返回一個新的謂詞函數(shù),滿足其中一個謂詞函數(shù)的條件

Predicate

negate

用于對謂詞函數(shù)取反,返回一個新的謂詞函數(shù),滿足相反的條件

UnaryOperator

andThen

用于從左到右組合兩個一元操作符,即:h(x) = g(f(x))

UnaryOperator

compose

用于從右到左組合兩個一元操作符,即:h(x) = f(g(x))

BinaryOperator

andThen

用于從左到右組合兩個二元操作符,即: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"));

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

分享到:
標(biāo)簽:Java
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定