函數式編程的中心思想:是把函數作為參數傳遞給另一個函數,或把函數作為一個返回值。
只是為了使程序適合有限的內存,程序員通過修改內存中的代碼來節省代碼空間,以便在程序執行時執行不同的操作。這種技術被稱為自修改代碼 (self-modifying code)
OO(object oriented,面向對象)是抽象數據,
FP(functional programming,函數式編程)是抽象行為
純粹的函數式語言在安全性方面更進一步。它強加了額外的約束,即所有數據必須是不可變的:設置一次,永不改變。
“不可變對象和無副作用”范式解決了並發編程中最基本和最棘手的問題之一
因此,經常提出純函數式語言作為並行編程的解決方案(還有其他可行的解決方案)。
1 新舊對比
package functional;
interface Strategy {
String approach(String msg);
}
class Soft implements Strategy {
@Override
public String approach(String msg) {
return msg.toLowerCase() + "?";
}
}
class Unrealted {
static String twice(String msg) {
return msg + " " + msg;
}
}
public class Strategize {
Strategy strategy;
String msg;
Strategize(String msg){
strategy = new Soft(); // [1]在 Strategize 中,Soft 作為默認策略,在構造函數中賦值。
this.msg = msg;
}
void communicate(){
System.out.println(strategy.approach(msg));
}
void changeStategy(Strategy strategy){
this.strategy = strategy;
}
public static void main(String[] args) {
Strategy[] strategies = {
new Strategy() {// [2]匿名內部類
@Override
public String approach(String msg) {
return msg.toUpperCase() + "!";
}
},
msg -> msg.substring(0,5),// [3]Java 8 的 Lambda 表達式
// 由箭頭 -> 分隔開參數和函數體,箭頭左邊是參數,箭頭右側是從 Lambda 返回的表達式,即函數體。這實現了與定義類、匿名內部類相同的效果,但代碼少得多。
Unrealted::twice// [4]Java 8 的方法引用,由 :: 區分。在 :: 的左邊是類或對象的名稱,在 :: 的右邊是方法的名稱,但沒有參數列表。
};
Strategize s = new Strategize("Hello there");
s.communicate();
for (Strategy newStrategy : strategies){//遍歷數組中的所有 Strategy
s.changeStategy(newStrategy);// [5]將每個 Strategy 放入 變量 s 中
s.communicate();// [6]產生不同的行為,具體取決於此刻正在使用的策略代碼對象.我們傳遞的是行為,而非僅數據。
}
}
}
/*
hello there?
HELLO THERE!
Hello
Hello there Hello there
*/
2 Lambda表達式
package functional;
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!"; //當只用一個參數,可以不需要括號 ()。 然而,這是一個特例。
static Body bod2 = (h) -> h + " More details"; //正常情況使用括號 () 包裹參數。 為了保持一致性,也可以使用括號 () 包裹單個參數,雖然這種情況並不常見。
static Description desc = () -> "Short info"; //如果沒有參數,則必須使用括號 () 表示空參數列表。
static Multi mult = (h, n) -> h + n; // 對於多個參數,將參數列表放在括號 () 中。
//到目前為止,所有 Lambda 表達式方法體都是單行。 該表達式的結果自動成為 Lambda 表達式的返回值,在此處使用 return 關鍵字是非法的。 這是 Lambda 表達式縮寫用於描述功能的語法的另一種方式。
static Description moreLines = () -> { //如果在 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()
*/
2.1 遞歸
遞歸方法必須是實例變量或靜態變量,否則會出現編譯時錯誤。
package functional;
interface IntCall{
int call(int arg);
}
階乘函數
package functional;
public class RecursiveFactorial {
static IntCall fact; //fact 是一個靜態變量
public static void main(String[] args) {
fact = n -> n == 0 ? 1: n* fact.call(n-1);
for (int i = 0; i <=10; i++)
System.out.println(fact.call(i));
}
}
/*
1
1
2
6
24
120
720
5040
40320
362880
3628800
*/
package functional;
public class RecursiveFibonacci {
IntCall fib; //實例變量
RecursiveFibonacci() {
fib = n -> n == 0 ? 0 :
n == 1 ? 1 :
fib.call(n - 1) + fib.call(n - 2);
}
int fibonacci(int n) {
return fib.call(n);
}
public static void main(String[] args) {
RecursiveFibonacci rf = new RecursiveFibonacci();
for (int i = 0; i <= 10; i++)
System.out.println(rf.fibonacci(i));
}
}
/*
0
1
1
2
3
5
8
13
21
34
55
*/
3 方法引用
Java 8 方法引用沒有歷史包袱。方法引用組成:類名或對象名,后面跟 :: ,然后跟方法名稱。
package functional;
interface Callable { // [1]
void call(String s);
}
class Describe {
void show(String msg) {// [2] show() 的簽名(參數類型和返回類型)符合 Callable 的 call() 的簽名。
System.out.println(msg);
}
}
public class MethodReferences {
static void hello(String name) {// [3] hello() 也符合 call() 的簽名。
System.out.println("Hello," + name);
}
static class Description {
String about;
Description(String desc) {
about = desc;
}
void help(String msg) { // [4] //help() 也符合,它是靜態內部類中的非靜態方法。
System.out.println(about + " " + msg);
}
}
static class Helper {
static void assist(String msg) {// [5] //assist() 是靜態內部類中的靜態方法。
System.out.println(msg);
}
}
public static void main(String[] args) {
Describe d = new Describe();
Callable c = d::show; // [6] 我們將 Describe 對象的方法引用賦值給 Callable ,它沒有 show() 方法,而是 call() 方法。 但是,Java 似乎接受用這個看似奇怪的賦值,因為方法引用符合 Callable 的 call() 方法的簽名。
c.call("call()"); // [7] 可以通過調用 call() 來調用 show(),因為 Java 將 call() 映射到 show()。
c = MethodReferences::hello; // [8] 靜態方法引用
c.call("Bob");
c = new Description("valueable")::help; // [9] 這是 [6] 的另一個版本:對已實例化對象的方法的引用,有時稱為綁定方法引用。
c.call("information");
c = Helper::assist; // [10] 獲取靜態內部類的方法引用的操作與 [8] 中外部類方式一樣
c.call("Help!");
}
}
/*
call()
Hello,Bob
valueable information
Help!
*/
3.1 Runnable接口
package functional;
class Go{
static void go(){
System.out.println("Go::go()");
}
}
public class RunnableMethodReference {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous");
}
}).start();
new Thread(() -> System.out.println("lambda")).start();//Lambda 表達式
new Thread(Go::go).start();//方法引用
}
}
/*
Anonymous
lambda
Go::go()
*/
3.2 未綁定的方法引用
未綁定的方法引用是指沒有關聯對象的普通(非靜態)方法。 使用未綁定的引用之前,我們必須先提供對象
package functional;
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) {
// 即使 make() 與 f() 具有相同的簽名,編譯也會報“invalid method reference”(無效方法引用)錯誤。
// 這是因為實際上還有另一個隱藏的參數:我們的老朋友 this。 你不能在沒有 X 對象的前提下調用 f()。
// 因此,X :: f 表示未綁定的方法引用,因為它尚未“綁定”到對象
// MakeString ms = X::f; // [1]
TransformX sp = X::f;
X x =new X();
System.out.println(sp.transform(x));// [2] [2] 的結果有點像腦筋急轉彎。 我接受未綁定的引用並對其調用 transform(),將其傳遞給 X,並以某種方式導致對 x.f() 的調用。 Java 知道它必須采用第一個參數,這實際上就是 this,並在其上調用方法。
System.out.println(x.f());// 同等效果
}
}
/*
X::f()
X::f()
*/
未綁定的方法與多參數的結合運用
package functional;
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');
}
}
3.2 構造函數引用
package functional;
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) {
MakeNoArgs mna = Dog::new;// [1]
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);
}
}
//我們如何對 [1],[2] 和 [3] 中的每一個使用 Dog :: new。 這 3 個構造函數只有一個相同名稱::: new,但在每種情況下都賦值給不同的接口。編譯器可以檢測並知道從哪個構造函數引用。
//
//編譯器能識別並調用你的構造函數( 在本例中為 make())。
4 函數式接口
每個接口只包含一個抽象方法,稱為函數式方法。
package functional;
@FunctionalInterface
interface Functional{
String goodbye(String arg);
}
interface FunctionalNoAnn{
String goodbye(String arg);
}
/*
@FunctionalInterface
interface NotFunctional{
String goodbye(String arg);
String hello(String arg);
}
產生錯誤信息:
NotFunctional is not a functional interface
multiple non-overriding abstract methods
found in interface NotFunctional
*/
public class FunctionalAnnotation {
public String goodbye(String arg){
return "Goodbye, " + arg;
}
public static void main(String[] args) {
FunctionalAnnotation fa = new FunctionalAnnotation();
// Java 8 在這里添加了一點小魔法:
// 如果將方法引用或 Lambda 表達式賦值給函數式接口(類型需要匹配),
// Java 會適配你的賦值到目標接口。
// 編譯器會自動包裝方法引用或 Lambda 表達式到實現目標接口的類的實例中。
Functional f = fa::goodbye;
FunctionalNoAnn fna = fa::goodbye;
// Functional fac = fa;// Incompatible
Functional f1 = a -> "Goodbye, " + a;
FunctionalNoAnn fnal = a -> "Goodbye, " + a;
}
}
基本命名准則:
如果只處理對象而非基本類型,名稱則為 Function,Consumer,Predicate 等。參數類型通過泛型添加。
如果接收的參數是基本類型,則由名稱的第一部分表示,如 LongConsumer,DoubleFunction,IntPredicate 等,但基本 Supplier 類型例外。
如果返回值為基本類型,則用 To 表示,如 ToLongFunction
如果返回值類型與參數類型一致,則是一個運算符:單個參數使用 UnaryOperator,兩個參數使用 BinaryOperator。
如果接收兩個參數且返回值為布爾值,則是一個謂詞(Predicate)。
如果接收的兩個參數類型不同,則名稱中有一個 Bi。
下面枚舉了基於 Lambda 表達式的所有不同 Function 變體的示例
package functional;
import java.util.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) {
Bar b = f1.apply(new Foo());
IBaz ib = f2.apply(11);
LBaz lb = f3.apply(11);
DBaz db = f4.apply(11);
int i = f5.applyAsInt(ib);
long l = f6.applyAsLong(lb);
double d = f7.applyAsDouble(db);
l = f8.applyAsLong(12);
d = f9.applyAsDouble(12);
i = f10.applyAsInt(12);
d = f11.applyAsDouble(12);
i = f12.applyAsInt(13.0);
l = f13.applyAsLong(13.0);
}
}
方法引用
package functional;
import java.util.function.BiConsumer;
class In1{}
class In2{}
public class MethodConversion {
static void accept(In1 i1,In2 i2){ // 只要參數類型、返回類型與 BiConsumer 的 accept() 相同即可。
System.out.println("accept()");
}
static void someOtherName(In1 i1,In2 i2){ //只要參數類型、返回類型與 BiConsumer 的 accept() 相同即可。
System.out.println("someOtherName()");
}
public static void main(String[] args) {
BiConsumer<In1,In2> bic;
bic = MethodConversion::accept;
bic.accept(new In1(),new In2());
bic = MethodConversion::someOtherName;
bic.accept(new In1(),new In2());
}
}
/*
accept()
someOtherName()
*/
基於類的函數式,應用於方法引用
創建最簡單的函數式簽名
package functional;
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 aa1,AA aa2){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());
Predicate<AA> p = ClassFunctionals::f7;
boolean result = p.test(new AA());
BiPredicate<AA,BB> bip = ClassFunctionals::f8;
result = bip.test(new AA(),new BB());
UnaryOperator<AA> uo = ClassFunctionals::f9;
AA aa = uo.apply(new AA());
BinaryOperator<AA> bo = ClassFunctionals::f10;
aa = bo.apply(new AA(),new AA());
}
}
4.1 多參數函數式接口
package functional;
@FunctionalInterface
public interface TriFunction<T,U,V,R> {
R apply(T t, U u, V v);
}
驗證 測試了方法引用和 Lambda 表達式
package functional;
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> tf = TriFunctionTest::f;
tf = (i,l,d) -> 12;
}
}
4.2 缺少基本類型的函數
package functional;
import java.util.function.BiConsumer;
public class BitConsumerPermutations {
static BiConsumer<Integer,Double> bicid =(i,d) -> System.out.printf("%d, %f%n",i,d); // %n 跨平台
static BiConsumer<Double,Integer> bicdi = (d,i) -> System.out.printf("%d, %f%n",i,d);
static BiConsumer<Integer,Long> bicil = (i, l) -> System.out.printf("%d, %d%n",i,l);
public static void main(String[] args) {
bicid.accept(47,11.34);
bicdi.accept(22.45,92);
bicil.accept(1,11L);
}
}
/*
47, 11.340000
92, 22.450000
1, 11
*/
package functional;
import java.util.function.Function;
import java.util.function.IntToDoubleFunction;
public class FunctionWithWrapped {
public static void main(String[] args) {
// Function<Integer,Double> fid = i -> i; // Integer cannot be converted to Double
Function<Integer,Double> fid = i -> (double)i;
IntToDoubleFunction fid2 = i -> i;
}
}
5 高階函數
高階函數(Higher-order Function)只是一個消費或產生函數的函數。
產生函數
package functional;
import java.util.function.Function;
interface FuncSS extends Function<String,String> {} // [1] 繼承
public class ProduceFunction {
static FuncSS produce(){
return s -> s.toLowerCase(); // [2] lambda表達式
}
public static void main(String[] args) {
FuncSS f = produce();
System.out.println(f.apply("YELLING"));
}
}
消費函數
package functional;
import java.util.function.Function;
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());
}
}
package functional;
import java.util.function.Function;
class I {
public I(){
System.out.println("Create I");
}
@Override
public String toString(){
return "I";
}
}
class O{
public O(){
System.out.println("Create O");
}
@Override
public String toString(){
return "O";
}
}
public class TransformFunction {
static Function<I,O> transform(Function<I,O> in){
System.out.println("2");
return in.andThen(o -> {
System.out.println(o);
return o;
});
}
public static void main(String[] args) {
System.out.println("1");
Function<I,O> f2 = transform(i -> {
System.out.println("lalla");
System.out.println(i);
return new O();
});
System.out.println("3");
O o = f2.apply(new I());
}
}
/*
1
2
3
Create I
lalla
I
Create O
*/
6 閉包
6.1 作為閉包的內部類
使用匿名內部類重寫之前的例子
package functional;
import java.util.function.IntSupplier;
public class AnonymousClosure {
IntSupplier makeFun(int x){
int i = 0;
// 同樣規則的應用:
// i++; // 非等同 final 效果
// x++; // 同上
return new IntSupplier() {
@Override
public int getAsInt() {
return x + i ;
}
};
}
}
7 函數組合
package functional;
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(),
f4 = f1.compose(f2).andThen(f3); // 創建的新函數 f4。它調用 apply() 的方式與常規幾乎無異.
// 當 f1 獲得字符串時,它已經被f2 剝離了前三個字符。這是因為 compose(f2) 表示 f2 的調用發生在 f1 之前。
public static void main(String[] args) {
System.out.println(f4.apply("GO AFTER ALL AMBULANCES"));
}
}
/*
AFTER ALL AMBULANCES
_fter _ll _mbul_nces
*/
package functional;
import java.util.function.Predicate;
import java.util.stream.Stream;
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);
public static void main(String[] args) {
Stream.of("bar","foobar","foobaz","fongopuckey")
.filter(p4)
.forEach(System.out::println);
}
}
/*
foobar
foobaz
*/
8 柯里化和部分求值
柯里化(Currying)名稱來自於其發明者之一 Haskell Curry
將一個多參數的函數,轉換為一系列單參數函數。
package functional;
import java.util.function.Function;
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; //[1] 這一連串的箭頭很巧妙。注意,在函數接口聲明中,第二個參數是另一個函數。
System.out.println(uncurried("Hi","Ho"));
Function<String,String> hi = sum.apply("Hi"); // [2]柯里化的目的是能夠通過提供一個參數來創建一個新函數,所以現在有了一個“帶參函數”和剩下的 “無參函數” 。實際上,你從一個雙參數函數開始,最后得到一個單參數函數。
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"));
}
}
/*HiHo
HiHo
Hup Ho
Hup Hey
*/
通過添加級別來柯里化一個三參數函數
package functional;
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"));
}
}
/*
對於每個級別的箭頭級聯(Arrow-cascading),你在類型聲明中包裹了另一個 Function。
Hi Ho Hup
*/
處理基本類型和裝箱時,請使用適當的 Function 接口
package functional;
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 純函數式編程
要確保一切是 final 的,同時你的所有方法和函數沒有副作用。因為 Java 在本質上並非是不可變語言,我們無法通過編譯器查錯。