这篇文章会介绍java中的函数式接口.
lambda表达式
java是不能把方法本身作为参数的. 在java支持lambda表达式之前, 我们想要实现"传入一个方法"的功能, 只能传入一个类, 然后调用这个类的方法实现功能, 当然为了通用性的考虑这里的参数类型通常定义为接口. 以排序方法为例(如List.sort
), 假设我们要对一个List
public class MyComparator implements Comparator {
int compareTo(Integer o) {
return o - this; //倒序排序
}
}
intList.sort(new MyComparator()); //或者写成内部类的形式, 不过都差不多
使用lambda表达式后, 上面的代码等价于intList.sort((a, b) -> b - a));
显然写起来更舒服了.
However, java中对于倒序排序其实也是有自己的方法的, 即intList.sort(Collections.reverseOrder())
在java中, 如果一个接口只有一个抽象方法, 那么这个接口就可以写成一个lambda表达式的形式, 也就是说我们实际上传入的, 是一个实现了这个接口的匿名内部类.
虽然lambda表达式让代码的可读性提高了一些, 但是我们仍然要定义一个接口传入这些类; jdk中提供了一些函数式接口, 可以实现大多数的功能.
...总感觉上面说了一堆废话, 其他人写的应该比我这几行详细多了.
Function
public interface Function {
R apply(T t);
}
其含义就是传入一个参数t, 返回类型为泛型R.
从接口意义上来说, 这个接口是下列所有函数式接口的基础.
类似的接口
-
IntFunction
等, 传入参数为int等基本类型, 返回类型为R; -
ToIntFunction
等, 传入类型为T, 返回类型为int; -
IntToDoubleFunction
等, 传入int, 返回double; -
BiFunction
, 传入类型为T和u, 返回类型为R; -
ToIntBiFunction
, 传入类型为T和U, 返回类型为int.
需要注意, 上面的方法和Funciton
本身并没有继承关系, 都是相互独立的.
Function接口中的其他方法
上面提到过, 有且仅有一个抽象方法的接口才能写成lambda表达式的形式. 那么为什么Function接口还能定义其他方法?
那是因为... 这几个方法是静态方法或非抽象方法... 如下所示:
//静态方法, 返回参数本身
static Function identity() {
return t -> t;
}
/*这两个default方法实现了函数式之间的衔接, andThen是后执行的方法, 而
compose是先执行的方法,
例如(b -> b.toC()).compose(a -> a.toB()).andThen(c -> c.toD())
完成了一个由a到d的转换
*虽然我还没有遇到过需要用到这个方法的情况, 但是感觉可能会有点用 */
default Function andThen(Function super R, ? extends V> after);
default Function compose(Function super V, ? extends T> before);
注意, BiFunction
只有andThen方法. 其余4种形式的接口没有以上方法. 从各接口的输入输出来看应当不难理解.
UnaryOperator
public interface UnaryOperator extends Function
从extends Function
可以看出, 这个方法是Function的一个简单写法, 即传入一个参数, 返回值类型和参数类型相同的方法.
这个方法当然也继承了andThen
和compose
; 此外, 由于静态方法不能继承所以在代码中重写了一遍, 其作用是相同的.
类似的接口
-
IntUnaryOperator
等, 传入int, 返回int, 如Arrays.setAll(a, IntUnaryOperator.identity())
会返回一个内容等于下标, 即[0..n-1]的数组 -
BinaryOperator
, 传入两个参数, 类型都为T, 返回参数也是类型T.
这种形式很容易想到max/min这种方法; 实际上BinaryOperator中也定义了两个静态方法
BinaryOperator minBy(Comparator super T>)
BinaryOperator maxBy(Comparator super T>)
*虽然看起来有用但是我也想不到什么时候用得到...
-
IntBinaryOperator
, 传入两个int, 返回值为int
Consumer
public interface Consumer {
void accept(T t);
default Consumer andThen(Consumer super T> after)
}
可以看出, Consumer定义的方法返回值为void, 但是给这个接口起名叫Consumer总感觉很微妙...
虽然Consumer无返回值, 但是定义了andThen
方法. 不过参数类型也限制为了Consumer. *其实不太懂这里为什么要这么做...
和普通方法一样, 有时候有返回值的方法也可以当做void方法来用, 比如list.add()
之类的, 这里也是一样.
类似的接口
-
IntConsumer
等, 输入int, 无返回值 -
BiConsumer
, 输入两个参数, 类型为T和U, 无返回值 -
ObjIntConsumer
, 输入参数分别为泛型T和int, 无返回值.
Predicate
public interface Predicate {
boolean test(T t);
}
返回类型为boolean的方法可以用Predicate作为函数式接口
Predicate也定义了下列非抽象方法
public static Predicate isEqual(Object)
default Predicate and(Predicate super T>)
default Predicate or(Predicate super T>)
default Predicate negate()
其具体意义看名字应该就能理解, 这里不再说明.
类似的接口
-
IntPridicate
, 输入int, 返回boolean -
BiPredicate
, 输入2个参数, 类型为T和U, 返回boolean
Supplier
public interface Supplier {
T get();
}
Supplier
public interface Supplier {
T get();
}
Supplier这个接口和上面几个有些区别. 我们注意到接口中的方法get()
没有传入参数, 但是会返回一个泛型T. 这种形式通常是不太常见的, 如果没有一个实例, 那我们该如何"凭空地"返回一个泛型T呢? 我想了一下, 通常有两种形式可以完成上述目标:
- 直接把lambda表达式外部的变量放进去, 比如:
String x = "abcde";
Supplier supplier = () -> x.toUpperCase();
但是在java中lambda表达式存在一些限制, 传入的外部参数必须是final, 所以如果传入一些基本类型的时候会比较麻烦. 当然, 传入外部参数时, 总是要注意闭包问题(请自行搜索闭包问题或者闭包错误).
- 返回这个类的一个空构造函数:
Supplier supplier = () -> new String();
也可以用简单的写法, 即Supplier
, 但是上面那个则不能写成这样的形式, 因为不能把x
传进去.
不过这样的话没有必要专门用一个函数式啊, 直接把类型设为String不就好了嘛... 不过考虑到这是一个泛型, 也许在一定程度上也能代替xxx.class
这样获取一个类然后再用反射拿到实例的用法?