函数式编程

algorain

在从Java 8开始,引入了函数式编程接口与Lambda表达式,可以让我们简化某些匿名内部类,编写更少的代码来实现功能,使用Lambda表达式可以简化匿名内部类的书写,但Lambda表达式并不能取代所有的匿名内部类,只能用来取代函数接口的简写,例如在创建一个线程时,常见的写法是这样:

1
2
3
4
5
6
new Thread(new Runnable(){// 接口名
@Override
public void run(){// 方法名
System.out.println("Thread run()");
}
}).start();

上述代码给Tread类传递了一个匿名的Runnable对象,重载Runnable接口的run()方法来实现相应逻辑。使用lambda表达式可以简化为如下形式:

1
2
3
new Thread(
() -> System.out.println("Thread run()")// 省略接口名和方法名
).start();

还是上面说的,使用Lambda的依据是必须有相应的函数式接口,函数式接口也是Java interface的一种,即内部只有一个抽象方法的接口,需要满足:

  • 一个函数式接口只有一个抽象方法(single abstract method);
  • Object类中的public abstract method不会被视为单一的抽象方法;
  • 函数式接口可以有默认方法和静态方法;
  • 函数式接口可以用@FunctionalInterface注解进行修饰。

这一点跟Java是强类型语言吻合,也就是不能在代码的任何地方写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。

自定义函数接口很容易,只需要编写一个只有一个抽象方法的接口即可。

1
2
3
4
@FunctionalInterface
public interface ConsumerInterface<T>{
void accept(T t);
}

上面代码中的@FunctionalInterface是可选的,但加上该标注编译器会帮你检查接口是否符合函数接口规范。就像加入@Override标注会检查是否重载了函数一样。

有了上述接口定义,就可以写出类似如下的代码:

1
ConsumerInterface<String> consumer = str -> System.out.println(str);

Java内部也提供了一些可以直接使用的函数接口

1
2
3
4
5
6
7
8
9
10
11
1. Consumer<T>     消费型接口 
void accept(T t) ,提供的是无返回值的抽象方法

2. Supplier <T> 供给型接口
T get() , 提供的是有返无参的抽象方法

3. Function<T,R> 函数型接口 T 是参数类型,R是返回值类型
R apply(T t) 提供的是 有参 有返的抽象方法

4.Predicate <T> 断言型接口
boolean test(T t) 提供的有参有返回的方法,返回的是boolean类型的返回值

这里要解释下什么是函数式编程

实际上,函数式编程没有一个严格的官方定义。严格上来讲,函数式编程中的“函数”,并不是指我们编程语言中的“函数”概念,而是指数学“函数”或者“表达式”(例如:y=f(x))。不过,在编程实现的时候,对于数学“函数”或“表达式”,我们一般习惯性地将它们设计成函数。所以,如果不深究的话,函数式编程中的“函数”也可以理解为编程语言中的“函数”。

每个编程范式都有自己独特的地方,这就是它们会被抽象出来作为一种范式的原因。面向对象编程最大的特点是:以类、对象作为组织代码的单元以及它的四大特性。面向过程编程最大的特点是:以函数作为组织代码的单元,数据与方法相分离。那函数式编程最独特的地方又在哪里呢?实际上,函数式编程最独特的地方在于它的编程思想。函数式编程认为程序可以用一系列数学函数或表达式的组合来表示。函数式编程是程序面向数学的更底层的抽象,将计算过程描述为表达式。不过,真的可以把任何程序都表示成一组数学表达式吗?

理论上讲是可以的。但是,并不是所有的程序都适合这么做。函数式编程有它自己适合的应用场景,比如科学计算、数据处理、统计分析等。在这些领域,程序往往比较容易用数学表达式来表示,比起非函数式编程,实现同样的功能,函数式编程可以用很少的代码就能搞定。但是,对于强业务相关的大型业务系统开发来说,费劲吧啦地将它抽象成数学表达式,硬要用函数式编程来实现,显然是自讨苦吃。相反,在这种应用场景下,面向对象编程更加合适,写出来的代码更加可读、可维护。

再具体到编程实现,函数式编程跟面向过程编程一样,也是以函数作为组织代码的单元。不过,它跟面向过程编程的区别在于,它的函数是无状态的。何为无状态?简单点讲就是,函数内部涉及的变量都是局部变量,不会像面向对象编程那样,共享类成员变量,也不会像面向过程编程那样,共享全局变量。函数的执行结果只与入参有关,跟其他任何外部变量无关。同样的入参,不管怎么执行,得到的结果都是一样的。这实际上就是数学函数或数学表达式的基本要求。

类似在断言中使用的就是将函数作为参数进行传递,函数本身并无状态

1
2
3
4
5
6
lbsWrapper.next(this::advPredicate, advWrapper);

public boolean advPredicate(Param param){
FeedsShowControl showControl = param.getParamByName(FeedsProcessEnum.SHOW_CONTROL.getName());
return showControl.getShowAdv();
}
  • Title: 函数式编程
  • Author: algorain
  • Created at: 2022-03-09 10:51:30
  • Updated at: 2023-05-14 22:10:20
  • Link: http://www.rain1024.com/2022/03/09/函数式编程/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments
On this page
函数式编程