Java8 API学习32 - java.util.function.Function, UnaryOperator, Consumer, Predicate, Supplier

这篇文章会介绍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.
从接口意义上来说, 这个接口是下列所有函数式接口的基础.

类似的接口

  1. IntFunction等, 传入参数为int等基本类型, 返回类型为R;
  2. ToIntFunction 等, 传入类型为T, 返回类型为int;
  3. IntToDoubleFunction等, 传入int, 返回double;
  4. BiFunction, 传入类型为T和u, 返回类型为R;
  5. 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 after);
default  Function compose(Function before);

注意, BiFunction只有andThen方法. 其余4种形式的接口没有以上方法. 从各接口的输入输出来看应当不难理解.

UnaryOperator

public interface UnaryOperator extends Function

extends Function可以看出, 这个方法是Function的一个简单写法, 即传入一个参数, 返回值类型和参数类型相同的方法.
这个方法当然也继承了andThencompose; 此外, 由于静态方法不能继承所以在代码中重写了一遍, 其作用是相同的.

类似的接口

  1. IntUnaryOperator等, 传入int, 返回int, 如Arrays.setAll(a, IntUnaryOperator.identity())会返回一个内容等于下标, 即[0..n-1]的数组
  2. BinaryOperator, 传入两个参数, 类型都为T, 返回参数也是类型T.
    这种形式很容易想到max/min这种方法; 实际上BinaryOperator中也定义了两个静态方法
BinaryOperator minBy(Comparator)
BinaryOperator maxBy(Comparator)

*虽然看起来有用但是我也想不到什么时候用得到...

  1. IntBinaryOperator, 传入两个int, 返回值为int

Consumer

public interface Consumer {
    void accept(T t);
    default Consumer andThen(Consumer after)
}

可以看出, Consumer定义的方法返回值为void, 但是给这个接口起名叫Consumer总感觉很微妙...
虽然Consumer无返回值, 但是定义了andThen方法. 不过参数类型也限制为了Consumer. *其实不太懂这里为什么要这么做...
和普通方法一样, 有时候有返回值的方法也可以当做void方法来用, 比如list.add()之类的, 这里也是一样.

类似的接口

  1. IntConsumer等, 输入int, 无返回值
  2. BiConsumer, 输入两个参数, 类型为T和U, 无返回值
  3. ObjIntConsumer, 输入参数分别为泛型T和int, 无返回值.

Predicate

public interface Predicate {
    boolean test(T t);
}

返回类型为boolean的方法可以用Predicate作为函数式接口
Predicate也定义了下列非抽象方法

public static Predicate isEqual(Object)

default Predicate and(Predicate)
default Predicate or(Predicate)
default Predicate negate()

其具体意义看名字应该就能理解, 这里不再说明.

类似的接口

  1. IntPridicate, 输入int, 返回boolean
  2. BiPredicate, 输入2个参数, 类型为T和U, 返回boolean

Supplier

public interface Supplier {
    T get();
}

Supplier

public interface Supplier {
    T get();
}

Supplier这个接口和上面几个有些区别. 我们注意到接口中的方法get()没有传入参数, 但是会返回一个泛型T. 这种形式通常是不太常见的, 如果没有一个实例, 那我们该如何"凭空地"返回一个泛型T呢? 我想了一下, 通常有两种形式可以完成上述目标:

  1. 直接把lambda表达式外部的变量放进去, 比如:
String x = "abcde";
Supplier supplier = () -> x.toUpperCase();

但是在java中lambda表达式存在一些限制, 传入的外部参数必须是final, 所以如果传入一些基本类型的时候会比较麻烦. 当然, 传入外部参数时, 总是要注意闭包问题(请自行搜索闭包问题或者闭包错误).

  1. 返回这个类的一个空构造函数:
Supplier supplier = () -> new String();

也可以用简单的写法, 即Supplier supplier = String::new, 但是上面那个则不能写成这样的形式, 因为不能把x传进去.
不过这样的话没有必要专门用一个函数式啊, 直接把类型设为String不就好了嘛... 不过考虑到这是一个泛型, 也许在一定程度上也能代替xxx.class这样获取一个类然后再用反射拿到实例的用法?

总结

  1. 由于泛型的缺陷, jdk额外定义了若干基本类型相关的方法;
  2. 如果上述方法没有你想要的, 那只能自己定义接口了, 比如说如果想要一个传入三个参数的方法, 就只能自己定义一个类似TriFunction之类的了.

你可能感兴趣的:(Java8 API学习32 - java.util.function.Function, UnaryOperator, Consumer, Predicate, Supplier)