Java 函数式编程的概念与实践

文章目录

    • 1. 命令式编程
    • 2. 声明式编程
    • 3. 函数式编程
    • 4. 函数接口
    • 5. Built-in 函数接口

参考
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;
    }
}

其中两个默认方法本身就是高阶函数,因为它们能接收函数并返回新的函数。

基本逻辑如下图:
Java 函数式编程的概念与实践_第1张图片

示例代码:

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, Predicate, Consumer

Steam 的 map 方法能接收 Function 函数对象作为参数:

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 = Integer, R = String。将 list 中所有的 value 转换为 String 类型。

同理,Stream 中的 filter 接收 Predicate 函数对象,forEach 接收 Comsumer 对象。

尽管 JDK 已经提供了足够丰富的函数接口,但有时仍然需要定制一些函数接口。

创建函数接口:

  • 为自定义的函数接口添加 @FunctionalInterface 注解,这是 JDK8 的惯例,编译器会对此类接口做校验;
  • 保证该接口只有一个抽象方法;
@FunctionalInterface
public interface Transformer<T> {
  T transform(T input);
}

你可能感兴趣的:(Java,编程语言)