什么是函数式编程


函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数! 函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为Lambda计算。


Java8内置了一些常用的方法接口​​FunctionalInterface​

这种接口只定义了一个抽象方法,并且用​​@FunctionalInterface​​注解标记,如​​Predicate,Consumer,Function,Supplier,Comparator​​等等,这些都属于​​java.util.function​​包中


@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
// 省略不贴了
复制代码

他们的特点是定义了函数的入参以及返回值,当使用时传入满足函数接口定义的表达式,即可通过编译器检查,下面会介绍函数接口和对应的4种使用方式

通过一个示例来看看使用函数式和不使用的区别,需求是要有一个函数,传入一个​​List<Integer>​​,筛选出单数的项,另一个则筛选出双数的项,先看看不使用函数式的写法


// 筛选出单数的方法
public static List<Integer> filterSingular(List<Integer> list) {
List<Integer> result = new ArrayList<>();
for (Integer item : list) {
if (item % 2 != 0) {
result.add(item);
}
}
return result;
}


// 筛选出双数的方法
public static List<Integer> filterEven(List<Integer> list) {
List<Integer> result = new ArrayList<>();
for (Integer item : list) {
if (item % 2 == 0) {
result.add(item);
}
}
return result;
}
复制代码

定义方法后调用,预期效果输出[1,3,5,7]和[2,4,5]


List<Integer> targetList = new ArrayList<Integer>() {
{
this.add(1);
this.add(2);
this.add(3);
this.add(4);
this.add(5);
this.add(6);
this.add(7);
}
};
List<Integer> singularList = filterSingular(targetList);
List<Integer> evenList = filterSingular(targetList);
System.out.println(singularList);
System.out.println(evenList);
复制代码

但其实这两个筛选函数,唯一区别只是判断条件的不同,这时候就可以将这个条件抽象成一个函数接口去编写,​​Predicate​​接口的​​test​​定义文章开头就有,传入一个泛型类型,返回一个​​boolean​​,改写下​​filter​​的代码


public static List<Integer> filter(List<Integer> list,Predicate<Integer> predicate) {
List<Integer> result = new ArrayList<>();
for (Integer item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
复制代码

将函数改造成了除了传入目前​​List​​外,还要传入一个实现了​​Predicate​​接口的实例对象,只需要传入满足函数定义入参和出参,就能通过编译,下面介绍4种这个函数的使用方式


  1. 使用传统的匿名内部类,在​​java8​​之前只能这么操作


List<Integer> singularList = filter(targetList, new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer % 2 != 0;
}
});
System.out.println(singularList);
复制代码


  1. 使用​​lambda​​表达式格式如下​​()->{}​​,​​()​​的是方法列表,​​->​​{}是方法体,由于目前只有一个参数,并且参数类型是可以推断出来的,所以类型和​​()​​可以不写,方法体只有一句,​​{}​​也可以不写,不推荐在方法体中写过长的代码,应保证可读性


List<Integer> singularList2 = filter(targetList, integer -> integer % 2 != 0);
// 下面是完整写法
// List<Integer> singularList3 = filter(targetList, (Integer integer) -> {
// return integer % 2 != 0;
// });
复制代码

可以使用的原因,​​lambda​​表达式满足传入​​Integer​​返回一个​​boolean​​的抽象操作,可以自动转化为函数接口 3. 静态方法引用,这里定义了一个静态方法,也可以自动的转化为函数接口,使用时需要用双冒号语法


private static boolean integerWithSingular (Integer haha){
return haha % 2 != 0;
}
复制代码

使用静态方法引用,Cn是所在类名,这种方式对比​​lambda​​表达式可以让可读性进一步提高,因为方法有名字,可以通过名字去判断在执行什么操作,并且更适合编写更多的逻辑


List<Integer> singularList3 = filter(targetList, Cn::integerWithSingular);
复制代码


  1. 实例方法,因为任何实例方法,第一个参数永远都是一个隐藏的指针​​this​​指向当前实例,由于上面例子泛型传入的是​​Integer​​类型,需要改写下预期才能演示,先声明一个类,并且有一个实例方法是完成传入​​Test​​类型返回​​boolean​​的映射


public class Test {
private long id;

public Test(long id) {
this.id = id;
}

private boolean integerWithSingular(){
return this.id % 2 != 0;
}
}
复制代码

将​​filter​​函数的​​Integer​​类型全换成​​Test​​类型


public static List<Test> filter(List<Test> list, Predicate<Test> predicate) {
List<Test> result = new ArrayList<>();
for (Test item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
复制代码

下面的调用中,传入​​类名::实例方法名​​实现的效果是等价的


ArrayList<Test> targetList = new ArrayList<Test>() {
{
new Test(1);
new Test(2);
}
};
filter(targetList,Test::integerWithSingular);
复制代码

任何只包含一个抽象方法的接口都可以被自动转换成函数接口,自己定义的接口没有标注@FunctionalInterface标注也可以

用的比较多的函数接口


  • ​Consumer​​ 输入一个对象,输出是空的,相当于消费掉传入的对象,​​ArrayList​​的​​forEach​​方法使用了​​Consumer​


// ArrayList的forEach方法源码
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
复制代码


  • ​Function​​更加接近于函数的定义,用于将一个类型变换成另一个类型,如数学中的函数把X变成Y,函数接口的定义如下,还是以刚才编写的​​Test​​类为理解,再编写一个​​map​​方法


public static String map(Test test, Function<Test, String> function) {
return function.apply(test);
}
复制代码

只要满足传入一个​​Test​​类型,返回一个​​String​​类型的东西都可以被自动转换


map(new Test(1),test -> "name");

// 如果Test类型还有一个属性为String的name和对应的getter方法,可以写成下面这种实例方法引用
// map(new Test(2), Test::getName);
复制代码


  • ​Supplier​​和Consumer是对立者,Consumer消费,Supplier提供,从虚空中提供一个东西


public static Object create(Supplier<Object> supplier){
return supplier.get();
}
复制代码

只要满足凭空冒出一个东西的条件即可


create(Object::new);
// new的作用也是从虚无创造出一个对象,所以可以这么写
create(() -> "supplier");
create(() -> new Test(1));
复制代码

最后再介绍函数式编程在排序中的使用


// Collections.sort的静态方法定义
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}

// Comparator.comparing的静态方法定义
// 理解成需要传入一个T类型映射到U类型的形式即可
// 对应着示例就是传入一个Test,返回一个实现了Comparable接口的对象(如Integer,String...)
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

复制代码

下面是爽快时间


// 使用简短的代码就能实现按对象中某个字段去排序
public static void main(String[] args) {
ArrayList<Test> tests = new ArrayList<Test>() {
{
this.add(new Test(2, "abc"));
this.add(new Test(1, "efg"));
}
};
// 现在Test实例的id字段排序,再将数组反转,然后再按照name字段排序
Collections.sort(tests, Comparator.comparing(Test::getId)
.reversed()
.thenComparing(Test::getName));
System.out.println(tests);
}
复制代码

其他的函数接口就不再赘述,只要搞懂原理,就能轻松上手使用