【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);
}