Java高阶函数
在阅读本文之前,建议先阅读Lambda表达式(不读也可以),如果你已经熟练使用Lambda表达式,那么请直接阅读本文。本文的代码也提供了多种写法:普通写法 -> Lambda表达式 。能够使用方法引用,也会使用方法引用。
高阶函数
高阶函数(higher-order Function):一个消费函数或者产生函数的函数;简而言之,就是一个函数的参数是函数,或者返回值是函数,那么这个函数就是高阶函数。
那么,什么样的参数或者返回值类型是函数类型的呢?如果该参数或者返回值类型是一个函数式接口
,那么这个参数或者返回值就是函数类型的。
下面来看一个例子,如何生产一个函数(返回值是函数式接口):
public class ProduceFunction {
Function<String, String> produce() {
//普通写法
// return new Function<String, String>() {
// @Override
// public String apply(String s) {
// return s.toLowerCase();
// }
// };
// Lambda表达式
// return s -> s.toLowerCase();
//方法引用
return String::toLowerCase;
}
public static void main(String[] args) {
ProduceFunction pf = new ProduceFunction();
Function<String, String> f = pf.produce();
System.out.println(f.apply("KEKE"));
}
}
/**
输出结果:
keke
*/
在这个例子中produce()
就是高阶函数
。有人会问,这不是个方法吗?没错,它就是个方法,函数式编程是Java借鉴面向函数式编程语言产生的一种新的适合Java的编程方式(不是Java特有),在其他语言中,方法也叫做函数,比如C语言,所以在函数式编程中,我们把方法叫做函数,只是这个方法的返回值类型或者参数类型是一个函数式接口。
现在再来看如何消费一个函数,消费函数需要在参数列表正确地描述函数类型 ,示例:
class One {}
class Two {}
public class ConsumeFunction {
static Two consume(Function<One, Two> function) {
return function.apply(new One());
}
public static void main(String[] args) {
//普通写法
Two two = consume(new Function<One, Two>() {
@Override
public Two apply(One one) {
return new Two();
}
});
// Lambda表达式
Two two1 = consume(one -> new Two());
}
}
再来看看基于消费函数生成新函数的例子,代码示例:
class I {
@Override
public String toString() {
return "I";
}
}
class O {
@Override
public String toString() {
return "O";
}
}
public class TransformFunction {
static Function<I, O> transform(Function<I, O> in) {
//普通写法 这里没有写错,就是Function<O, O>
// return in.andThen(new Function<O, O>() {
// @Override
// public O apply(O o) {
// System.out.println(o);
// return o;
// }
// });
//Lambda表达式
return in.andThen(o -> {
System.out.println(o);
return o;
});
}
public static void main(String[] args) {
//普通写法
Function<I, O> f = transform(new Function<I, O>() {
@Override
public O apply(I i) {
System.out.println(i);
return new O();
}
});
O o = f.apply(new I());
// Lambda表达式
Function<I, O> f2 = transform(i -> {
System.out.println(i);
return new O();
});
O o2 = f2.apply(new I());
}
}
/**
运行结果:
I
O
I
O
*/
上面的两个例子都通俗易懂,这个例子,看着可能就有点懵逼了,不急,先来看看Function这个函数式接口的源码:
/**
* 代表这一个方法,能够接受参数,并且返回一个结果
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* 将参数赋予给相应方法
*
* @param t
* @return
*/
R apply(T t);
/**
* 先执行参数(即也是一个Function)的,再执行调用者(同样是一个Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* 先执行调用者,再执行参数,和compose相反。
*/
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;
}
}
以上是Function
这个接口的具体代码,并简要介绍了四个方法的功能。
这么看还是太抽象了,现在来看一个具体例子:
public class Test {
public static void main(String[] args) {
//普通写法
// Function<Integer, Integer> f1 = new Function<Integer, Integer>() {
// @Override
// public Integer apply(Integer i) {
// return i + 1;
// }
// };
// Lambda表达式
Function<Integer, Integer> f1 = i -> i + 1;
Function<Integer, Integer> g1 = i -> i * 2;
System.out.println(f1.apply(2)); // ---- ①
System.out.println(g1.apply(2)); // ---- ②
System.out.println(f1.andThen(g1).apply(3)); // ---- ③
System.out.println(f1.compose(g1).apply(3)); // ---- ④
System.out.println(Function.identity().compose(g1).apply(4)); // ---- ⑤
}
/**
输出结果:
3
4
8
7
8
*/
}
输出①、②比较容易理解,就是简单的把参数赋值到apply()
里,经过运算返回结果。
输出③中调用了andThen()
,f1.andThen(g1).apply(3)
的执行顺序就是先执行调用者f1
,然后再执行参数g1
。这样不是很直观,那么我们可以把它看成是这样的形式:g1.apply(f1.apply(3))
这个不就是数学中的函数g(f(x))
吗,所以优先执行内部的f(x)
也就是f1.apply(3)
,然后将执行结果作为参数继续执行g(x)
也就是g1.apply()
。
输出④中调用了compose()
,它的执行顺序刚好和andThen()
相反,f1.compose(g1).apply(3)
的执行顺序是先执行参数g1
,然后再执行调用者f1
。那么f1.compose(g1).apply(3)
就可以看成这样的形式:f1.apply(g1.apply(3))
;也就可以看成数学函数f(g(x))
,这时先执行g(x)
也就是g1.apply(3)
,然后将结果作为参数继续执行f(x)
也就是f1.apply()
。
输出⑤中的identity()
是一个静态方法,它用于返回正在执行的方法,所以Function.identity().compose(g1).apply(4)
就相当于g1.apply(4)
。
有了这些背景知识,我们回过头再来看看基于消费函数生成新函数的例子:f.apply(new I());
。这个代码实际上可以写成:
// f就是.apply(new I())前面的一大串东西。
new Function<I, O>() {
@Override
public O apply(I i) {
System.out.println(i);
return new O();
}
}.andThen(new Function<O, O>() {
@Override
public O apply(O o) {
System.out.println(o);
return o;
}
}).apply(new I());
可以看到这里调用了andThen()
,那么根据上面的执行顺序,应该先执行调用者,也就是f1.apply(new I())
,返回值为O
类型,那么将返回值再次作为参数传入andThen()
中的apply()
中,那么就得到了最终的输出结果I
和O
。