Skip to content

【OnJava-十三】函数式编程

为了让代码更加一致和容易维护,编写大量代码,从易于理解、充分测试及可靠的现有小块开始,最后将它们组合在一起以创建新代码。

这就是函数式编程(FP)的意义所在。通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,一些非函数式语言已经习惯了使用函数式编程产生的优雅的语法。

纯粹的函数式语言在安全性方面更进一步。它强加了额外的约束,即所有数据必须是不可变的:设置一次,永不改变。将值传递给函数,该函数然后生成新值但从不修改自身外部的任何东西(包括其参数或该函数范围之外的元素)。当强制执行此操作时,你知道任何错误都不是由所谓的副作用引起的,因为该函数仅创建并返回结果,而不是其他任何错误。

java
package xx;  
  
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();  
        this.msg = msg;  
    }  
  
    void communicate() {  
        System.out.println(strategy.approach(msg));  
    }  
  
    void changeStrategy(Strategy strategy) {  
        this.strategy = strategy;  
    }  
  
    public static void main(String[] args) {  
  
        // interface 实现  
        Strategy[] strategies = {  
		        // 一种略显冗长且更自发的方法是创建一个`匿名内部类`
                new Strategy() {  
                    public String approach(String msg) {  
                        return msg.toUpperCase() + "!";  
                    }  
                },  
                // Java 8 的 Lambda 表达式。由箭头 `->` 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。
                msg -> msg.substring(0, 5),  
                // Java 8 的**方法引用**,由 `::` 区分。在 `::` 的左边是类或对象的名称,在 `::` 的右边是方法的名称,但没有参数列表。
                Unrelated::twice  
        };  
  
        Strategize s = new Strategize("Hello there");  
        s.communicate();  
        
		// 逐步遍历数组中的所有 **Strategy**,并使用 `changeStrategy()` 方法将每个 **Strategy** 放入 变量 `s` 中。
        for (Strategy newStrategy : strategies) {  
            s.changeStrategy(newStrategy);  
            s.communicate();  
        }  
    }  
}
package xx;  
  
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();  
        this.msg = msg;  
    }  
  
    void communicate() {  
        System.out.println(strategy.approach(msg));  
    }  
  
    void changeStrategy(Strategy strategy) {  
        this.strategy = strategy;  
    }  
  
    public static void main(String[] args) {  
  
        // interface 实现  
        Strategy[] strategies = {  
		        // 一种略显冗长且更自发的方法是创建一个`匿名内部类`
                new Strategy() {  
                    public String approach(String msg) {  
                        return msg.toUpperCase() + "!";  
                    }  
                },  
                // Java 8 的 Lambda 表达式。由箭头 `->` 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。
                msg -> msg.substring(0, 5),  
                // Java 8 的**方法引用**,由 `::` 区分。在 `::` 的左边是类或对象的名称,在 `::` 的右边是方法的名称,但没有参数列表。
                Unrelated::twice  
        };  
  
        Strategize s = new Strategize("Hello there");  
        s.communicate();  
        
		// 逐步遍历数组中的所有 **Strategy**,并使用 `changeStrategy()` 方法将每个 **Strategy** 放入 变量 `s` 中。
        for (Strategy newStrategy : strategies) {  
            s.changeStrategy(newStrategy);  
            s.communicate();  
        }  
    }  
}

Lambda 表达式

Lambda 表达式是使用最小可能语法编写的函数定义:

  1. Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们 “只是函数”。
  2. Lambda 语法尽可能多,这正是为了使 Lambda 易于编写和使用。
java
package xx;  
  
interface Description {  
    String brief();  
}  
interface Body {  
    String detailed(String head);  
}  
interface Multi {  
    String twoArg(String head, Double d);  
}  
  
public class LambdaExpressions {  
    // 生成方法:  
    //     协议 协议名字 = lambda    
    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;  
    static Description moreLines = () -> {  
        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());  
    }  
}
package xx;  
  
interface Description {  
    String brief();  
}  
interface Body {  
    String detailed(String head);  
}  
interface Multi {  
    String twoArg(String head, Double d);  
}  
  
public class LambdaExpressions {  
    // 生成方法:  
    //     协议 协议名字 = lambda    
    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;  
    static Description moreLines = () -> {  
        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());  
    }  
}

方法引用

Java 8 方法引用没有历史包袱。方法引用组成:类名或对象名,后面跟 :: ,然后跟方法名称。

java
class Describe {  
    void show(String msg) {  
        System.out.println(msg);  
    }  
}  
interface Callable {  
    void call(String s);  
}  
  
public class LambdaExpressions {  
    public static void main(String[] args) {  
        Describe d = new Describe();  
        Callable c = d::show;  
        c.call("call()");  
    }  
}
class Describe {  
    void show(String msg) {  
        System.out.println(msg);  
    }  
}  
interface Callable {  
    void call(String s);  
}  
  
public class LambdaExpressions {  
    public static void main(String[] args) {  
        Describe d = new Describe();  
        Callable c = d::show;  
        c.call("call()");  
    }  
}

构造函数引用

::new

java
class Dog {
  String name;
  int age = -1; 
  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; 
    Make1Arg m1a = Dog::new;   
    Make2Args m2a = Dog::new;  
    Dog dn = mna.make();
    Dog d1 = m1a.make("Comet");
    Dog d2 = m2a.make("Ralph", 4);
  }
}
class Dog {
  String name;
  int age = -1; 
  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; 
    Make1Arg m1a = Dog::new;   
    Make2Args m2a = Dog::new;  
    Dog dn = mna.make();
    Dog d1 = m1a.make("Comet");
    Dog d2 = m2a.make("Ralph", 4);
  }
}

函数式接口

函数式接口

  1. 当然首先是一个接口
  2. 然后就是在这个接口里面只能有一个抽象方法
  3. 可包含默认方法 defaul xxx
  4. 可包含允许定义静态方法
  5. 可定义java.lang.Object里的public方法

Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。 提醒编译器去检查该接口是否仅包含一个抽象方法。

java
@FunctionalInterface
interface GreetingService {
    // 只能一个抽象方法
	void sayMessage(String message); 
	// 可用定义静态方法
	static void printHello(){
		System.out.println("Hello");
	}
	// 可定义默认方法
	default void doSomeMoreWork1(){
        // Method body
    }
    // 可定义java.lang.Object里的public方法
    @Override
    boolean equals(Object obj);
}
@FunctionalInterface
interface GreetingService {
    // 只能一个抽象方法
	void sayMessage(String message); 
	// 可用定义静态方法
	static void printHello(){
		System.out.println("Hello");
	}
	// 可定义默认方法
	default void doSomeMoreWork1(){
        // Method body
    }
    // 可定义java.lang.Object里的public方法
    @Override
    boolean equals(Object obj);
}

使用举例

java
@FunctionalInterface  
interface Functional {  
    String goodbye(String arg);  
}  

interface FunctionalNoAnn {  
    String goodbye(String arg);  
}  

public class LamdaEx {  
    public String goodbye(String arg) {  
        return "Goodbye, " + arg;  
    }  
    public static void main(String[] args) {  
        LamdaEx fa = new LamdaEx();  
        Functional f = fa::goodbye;  
        FunctionalNoAnn fna = fa::goodbye;  
        Functional fl = a -> "Goodbye, " + a;  
        FunctionalNoAnn fnal = a -> "Goodbye, " + a;  
    }  
}
@FunctionalInterface  
interface Functional {  
    String goodbye(String arg);  
}  

interface FunctionalNoAnn {  
    String goodbye(String arg);  
}  

public class LamdaEx {  
    public String goodbye(String arg) {  
        return "Goodbye, " + arg;  
    }  
    public static void main(String[] args) {  
        LamdaEx fa = new LamdaEx();  
        Functional f = fa::goodbye;  
        FunctionalNoAnn fna = fa::goodbye;  
        Functional fl = a -> "Goodbye, " + a;  
        FunctionalNoAnn fnal = a -> "Goodbye, " + a;  
    }  
}

个人理解是为lamda参数的类型推断提供了依据。@FunctionalInterface 限制了不可能出现重载方法?

多参数函数式接口

java.util.functional 中的接口是有限的。比如有了 BiFunction,但它不能变化。 如果需要 三参数函数 的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例:

@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> tf =
TriFunctionTest::f;
tf = (i, l, d) -> 12;
}
}

这里我们测试方法引用和 Lambda 表达式。

高阶函数

这个名字听起来有点令人生畏,但是:高阶函数(Higher-order Function)只是一个消费或产生函数的函

java
import java.util.function.*;  
  
interface FuncSS extends Function<String, String> {  
}  
  
public class ProduceFunction {  
    static FuncSS produce() {  
        return s -> s.toLowerCase();  
    }  
  
    public static void main(String[] args) {  
        FuncSS f = produce();  
        System.out.println(f.apply("YELLING"));  
  
        FuncSS f1 = (item) -> item + " Good";  
        System.out.println(f1.apply("Hell"));  
    }  
}
import java.util.function.*;  
  
interface FuncSS extends Function<String, String> {  
}  
  
public class ProduceFunction {  
    static FuncSS produce() {  
        return s -> s.toLowerCase();  
    }  
  
    public static void main(String[] args) {  
        FuncSS f = produce();  
        System.out.println(f.apply("YELLING"));  
  
        FuncSS f1 = (item) -> item + " Good";  
        System.out.println(f1.apply("Hell"));  
    }  
}

要消费一个函数,消费函数需要在参数列表正确地描述函数类型。代码示例:

java
import java.util.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());
  }
}
import java.util.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());
  }
}
  • andThen
    • 返回一个先执行当前函数对象apply方法再执行after函数对象apply方法的函数对象。
  • compose
    • 返回一个先执行before函数对象apply方法再执行当前函数对象apply方法的函数对象
dart
//返回一个先执行当前函数对象apply方法再执行after函数对象apply方法的函数对象。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
	Objects.requireNonNull(after);
	return (T t) -> after.apply(apply(t));
}
 
//返回一个先执行before函数对象apply方法再执行当前函数对象apply方法的函数对象
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
	Objects.requireNonNull(before);
	return (V v) -> apply(before.apply(v));
}
//返回一个先执行当前函数对象apply方法再执行after函数对象apply方法的函数对象。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
	Objects.requireNonNull(after);
	return (T t) -> after.apply(apply(t));
}
 
//返回一个先执行before函数对象apply方法再执行当前函数对象apply方法的函数对象
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
	Objects.requireNonNull(before);
	return (V v) -> apply(before.apply(v));
}