函數語言程式設計的理論基礎是阿隆佐·丘奇(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 用法:
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!
使用 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);
}
}
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
的函數式介面的基本使用基本準測,如下
下面列舉了基於 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) |
在使用函數介面時,名稱無關緊要——只要引數型別和返回型別相同。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(),它們的區別如下:
輸出結果:
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() 中:
輸出結果:
foobar
foobaz
在 java.util.function
中常用的支援函陣列合的方法,大致如下:
函數式介面 | 方法名 | 描述 |
---|---|---|
Function<T, R> | andThen | 用於從左到右組合兩個函數,即:h(x) = g(f(x)) |
Function<T, R> | compose | 用於從右到左組合兩個函數,即:h(x) = f(g(x)) |
Consumer |
andThen | 用於從左到右組合兩個消費者,按順序執行兩個消費者操作 |
Predicate |
and | 用於組合兩個謂詞函數,返回一個新的謂詞函數,滿足兩個謂詞函數的條件 |
Predicate |
or | 用於組合兩個謂詞函數,返回一個新的謂詞函數,滿足其中一個謂詞函數的條件 |
Predicate |
negate | 用於對謂詞函數取反,返回一個新的謂詞函數,滿足相反的條件 |
UnaryOperator | andThen | 用於從左到右組合兩個一元操作符,即:h(x) = g(f(x)) |
UnaryOperator | compose | 用於從右到左組合兩個一元操作符,即:h(x) = f(g(x)) |
BinaryOperator | andThen | 用於從左到右組合兩個二元操作符,即: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
Lambda 表示式和方法參照並沒有將 Java 轉換成函數式語言,而是提供了對函數語言程式設計的支援(Java 的歷史包袱太重了),這些特性滿足了很大一部分的、羨慕 Clojure 和 Scala 這類更函數化語言的 Java 程式設計師。阻止了他們投奔向那些語言(或者至少讓他們在投奔之前做好準備)。總之,Lambdas 和方法參照是 Java 8 中的巨大改進