1. 程式人生 > >【Java 程式設計】函數語言程式設計一點通

【Java 程式設計】函數語言程式設計一點通

文章目錄


參考
https://developer.ibm.com/articles/j-java8idioms1/
https://developer.ibm.com/articles/j-java8idioms7/

1. 指令式程式設計

大部分程式設計師更習慣命令式(imperative)的程式設計風格,既要告訴程式做什麼,也要說明怎麼做

例如:

public
static void doSomething(List<String> names) { boolean found = false; for(String name : names) { if(name.equals("AAA")) { found = true; break; } } if(found) System.out.println("Do something if found"); else System.out.println("Do nothing if not found"
); } }

上面就是一個典型的命令式程式,程式中既有如何查詢目標物件的邏輯,又有找到後做什麼的邏輯!

結果就造成程式碼拖沓、冗長、低效

2. 宣告式程式設計

宣告式(declarative),顧名思義就是宣告做什麼,不關心、也不負責怎麼做

例如:

public static void doSomething(List<String> names) {
    if(names.contains("AAA"))
      System.out.println("Do something if found");
    else
      System.
out.println("Do nothing if not found"); } }

怎麼做的問題交由 contains 解決!

3. 函數語言程式設計

函式式(functional)程式設計風格是一種更高效、更簡潔的宣告式風格!

在這裡插入圖片描述

高階函式(higher order function)是指能夠接受(函式引數)、建立(函式物件)、或返回函式的函式!在 JavaScript 或者 Python 中,函式就是物件,是一等公民。而 Java 有所不同,Java 使用 functional interfaces 來建立函式物件(或者 lambda 表示式)。

例如:

public class Demo{
    interface Action{
        public void doSomething();
    }

    public static void demoFunctional() {
        receiveFunction(returnFunction()); // 輸出 Demo higher order function!
    }

    // 接收 Function
    public static void receiveFunction(Action action){
        action.doSomething();
    }

    // 返回 Function
    public static Action returnFunction(){
        return () -> System.out.println("Demo higher order function!");
    }

    public static void main(String[] args){
        demoFunctional();
    }
}

在 Java 中進行函數語言程式設計需要使用函式介面!

4. 函式介面

示例:

interface Runnable {
 void run();
}

函式介面(functional interface)需要滿足以下特點:

  • 只包含一個抽象方法;
  • 方法名不能與 Object 中的方法衝突:不能為 equals(), toString() 等;
  • 函式介面中可以包含 default 和 static 方法;

下面的介面不是函式式介面:

// 方法名不能是 equals
interface NonFunc {
 boolean equals(Object obj);
}

// 不能有兩個抽象方法 
interface Foo {
 int m();
 Object clone();
}

例如,下面的函式介面為 JDK8 Built-in 介面,apply 為抽象方法, compose, andThen 為 default 方法,identity 為 static 方法:

public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

其中兩個預設方法本身就是高階函式,因為它們能接收函式並返回新的函式。

基本邏輯如下圖:
在這裡插入圖片描述

示例程式碼:

public class Demo
{
    public static void main( String[] args )
    {
        Function<String, Integer> caller = (s) -> Integer.parseInt(str);
        Function<Integer, String> callee = (i) -> Integer.toString(i + 5);

        Function<String, String> andThenFun = caller.andThen(callee);
        Function<Integer, Integer> composeFun = caller.compose(callee);

        System.out.println(andThenFun.apply("10"));
        System.out.println(composeFun.apply(10));
    }
}

首先要清楚函式的執行順序,然後是匹配輸入和輸出的引數型別。

Java 是靜態型別語言,因此在進行函數語言程式設計時肯定不如 python 或 js 那樣自由、靈動,型別匹配繞來繞去,顯得囉裡囉嗦。

5. Built-in 函式介面

Function<T, R>, Predicate, Consumer

Steam 的 map 方法能接收 Function<T, R> 函式物件作為引數:

public class Demo{
    public static void main( String[] args )
    {
        List<Integer> list = Arrays.asList(1,2,3,4,5,6);

        list.stream().map(i -> Integer.toString(i))
                .forEach(s -> System.out.println(String.format("class: %s, value: %s", s.getClass(), s)));
        
    }
}

Function<T, R>: 表示函式物件接收型別為 T 的引數,返回型別為 R 的引數,上例中 T = Integer, R = String。將 list 中所有的 value 轉換為 String 型別。

同理,Stream 中的 filter 接收 Predicate 函式物件,forEach 接收 Comsumer 物件。

儘管 JDK 已經提供了足夠豐富的函式介面,但有時仍然需要定製一些函式介面。

建立函式介面:

  • 為自定義的函式介面新增 @FunctionalInterface 註解,這是 JDK8 的慣例,編譯器會對此類介面做校驗;
  • 保證該介面只有一個抽象方法;
@FunctionalInterface
public interface Transformer<T> {
  T transform(T input);
}