【小家java】java8新特性之---函数式接口(Supplier、Consumer、Predicate、Function、UnaryOperator,通往高阶设计的好工具)

相关阅读

【小家java】java8新特性(简述十大新特性)
【小家java】java8新特性之—Base64加密和解密原理
【小家java】java8新特性之—反射获取方法参数名
【小家java】java8新特性之—全新的日期、时间API(完全实现了JSR 310规范)
【小家java】java8新特性之—Optional的使用,避免空指针,代替三目运算符
【小家java】java8新特性之—lambda表达式的的原理
【小家java】java8新特性之—函数式接口(Supplier、Consumer、Predicate、Function、UnaryOperator,通往高阶设计的好工具)
【小家java】java8新特性之—方法引用
【小家java】java8新特性之—Stream API 详解 (Map-reduce、Collectors收集器、并行流)
【小家java】java8新特性之—外部迭代和内部迭代(对比性能差异)


什么是函数式接口?

所有函数式接口都在这个包:java.util.function

首先,它还是一个接口,所以必须满足接口最基本的定义。但它是一个特殊的接口:SAM类型的接口(Single Abstract Method)。可以在调用时,使用一个lambda表达式作为参数。
定义要求:

  1. 只能有一个抽象方法需要被实现
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

备注:此处不包括与Object的public方法(clone方法不行,因为clone方法是protected,编译会报错)重名的方法。当然里面的默认方法、static方法都是无所谓的

default 修饰的默认方法方法,这个关键字是Java8中新增的,为的目的就是使得某一些接口,原则上只有一个方法被实现,但是由于历史原因,不得不加入一些方法来兼容整个JDK中的API,所以就需要使用default关键字来定义这样的方法

  1. 可以有从Object继承过来的抽象方法,因为所有类的最终父类都是Object
  2. 接口中唯一抽象方法的命名并不重要,因为函数式接口就是对某一行为进行抽象,主要目的就是支持Lambda表达式。

以下附JDK 8之前已有的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

Java8还提供了@FunctionalInterface注解来帮助我们标识函数式接口。所以Java8后上面那些接口都被打上了这个标记。

下面给出一张图:说出Java8新提供的函数式接口们(可以满足99%需求):
这里写图片描述
四大核心函数式接口:
这里写图片描述

public interface Supplier

其简洁的声明,会让人以为不是函数。这个抽象方法的声明,同Consumer相反,是一个只声明了返回值,不需要参数的函数(这还叫函数?)。也就是说Supplier其实表达的不是从一个参数空间到结果空间的映射能力,而是表达一种生成能力。

Supplier<String> supplier = String::new;

其他Supplier扩展接口:

  • BooleanSupplier:boolean getAsBoolean();返回boolean
  • DoubleSupplier:double getAsDouble();返回double
  • IntSupplier:int getAsInt();返回int
  • LongSupplier:long getAsLong();返回long
public interface Consumer

这个接口声明太重要了,应用场景太多了。因为需要返回值的我们用Function,不需要返回值的,我们用它就可。

Consumer consumer = System.out::println;

看其源码 还有个默认方法andThen:

void accept(T t);
 default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

andThen可以实现消费两次。消费一次后,继续消费一次。使用场景:

其他Consumer扩展接口:

  • BiConsumer:void accept(T t, U u);接受两个参数
  • DoubleConsumer:void accept(double value);接受一个double参数
  • IntConsumer:void accept(int value);接受一个int参数
  • LongConsumer:void accept(long value);接受一个long参数
  • ObjDoubleConsumer:void accept(T t, double value);接受一个泛型参数一个double参数
  • ObjIntConsumer:void accept(T t, int value);接受一个泛型参数一个int参数
  • ObjLongConsumer:void accept(T t, long value);接受一个泛型参数一个long参数
public interface Predicate

断言接口,有点意思了。其默认方法也封装了and、or和negate逻辑 和一个静态方法isEqual。

//and方法接收一个Predicate类型,也就是将传入的条件和当前条件以并且的关系过滤数据。
default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}

//or方法同样接收一个Predicate类型,将传入的条件和当前的条件以或者的关系过滤数据
default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) || other.test(t);
}

//negate就是将当前条件取反
default Predicate<T> negate() {
    return (t) -> !test(t);
}

static <T> Predicate<T> isEqual(Object targetRef) {
    return (null == targetRef)
            ? Objects::isNull
            : object -> targetRef.equals(object);
}

看几个案例:

public List<Integer> conditionFilterAnd(List<Integer> list, Predicate<Integer> predicate,Predicate<Integer> predicate2){
    return list.stream().filter(predicate.and(predicate2)).collect(Collectors.toList());
}

public List<Integer> conditionFilterOr(List<Integer> list, Predicate<Integer> predicate,Predicate<Integer> predicate2){
    return list.stream().filter(predicate.or(predicate2)).collect(Collectors.toList());
}
public List<Integer> conditionFilterNegate(List<Integer> list, Predicate<Integer> predicate){
    return list.stream().filter(predicate.negate()).collect(Collectors.toList());
}

//大于5并且是偶数
result = predicateTest.conditionFilterAnd(list, integer -> integer > 5, integer1 -> integer1 % 2 == 0);
result.forEach(System.out::println);//6 8 10
System.out.println("-------");

//大于5或者是偶数
result = predicateTest.conditionFilterOr(list, integer -> integer > 5, integer1 -> integer1 % 2 == 0);
result.forEach(System.out::println);//2 4 6 8 9 10
System.out.println("-------");

//条件取反
result = predicateTest.conditionFilterNegate(list,integer2 -> integer2 > 5);
result.forEach(System.out::println);// 1 2 3 4 5
System.out.println("-------");

最后再来看一下Predicate接口中的唯一一个静态方法(小纵范围使用):

isEqual方法返回类型也是Predicate,也就是说通过isEqual方法得到的也是一个用来进行条件判断的函数式接口实例。而返回的这个函数式接口实例是通过传入的targetRef的equals方法进行判断的。我们看一下具体

public static void main(String[] args) {
        System.out.println(Predicate.isEqual("test").test("test")); //true
        System.out.println(Predicate.isEqual(null).test("test")); //false
        System.out.println(Predicate.isEqual(null).test(null)); //true
        System.out.println(Predicate.isEqual(1).test(new Integer(1))); //true
        //注意 这里是false的
        System.out.println(Predicate.isEqual(new Long(1)).test(new Integer(1))); //false
    }

其他Predicate扩展接口:

  • BiPredicate:boolean test(T t, U u);接受两个参数的,判断返回bool
  • DoublePredicate:boolean test(double value);入参为double的谓词函数
  • IntPredicate:boolean test(int value);入参为int的谓词函数
  • LongPredicate:boolean test(long value);入参为long的谓词函数
public interface Function

这个接口非常非常总要。是很上层的一个抽象。除了一个抽象方法apply外,其默认实现了3个default方法,分别是compose、andThen和identity。

  default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

 default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

compose 和 andThen 的不同之处是函数执行的顺序不同。andThen就是按照正常思维:先执行调用者,再执行入参的。然后compose 是反着来的,这点需要注意。

看看唯一的一个静态方法identity:

static <T> Function<T, T> identity() {
        return t -> t;
    }

我们会发现,identity啥都没做,只是返回了一个Function方法,并且是两个泛型都一样的方法,意义着实不是太大。下面看一个复杂点的例子,各位感受一下:

 public static void main(String[] args) {
        Function<Integer, Integer> times2 = i -> i * 2; //加倍函数
        Function<Integer, Integer> squared = i -> i * i; //平方函数

        System.out.println(times2.apply(4)); //8
        System.out.println(squared.apply(4)); //16

        System.out.println(times2.compose(squared).apply(4));  //32   先4×4然后16×2, 先执行参数,再执行调用者
        System.out.println(times2.andThen(squared).apply(4));  //64   先4×2,然后8×8, 先执行调用者,再执行参数

        //看看这个例子Function.identity()构建出一个恒等式函数而已,方便方法的连缀 这就是它的唯一优点
        System.out.println(Function.identity().compose(squared).apply(4));   //16 先执行4*4,再执行identity 值不变
    }

由Function,可以扩展出高阶函数。如泛型中有个类型还是Function,这种需要还是经常有的,所以BiFunction提供了二元函数的一个接口声明

  public static void main(String[] args) {
        BiFunction<Integer, Integer, Integer> biFunction = (x, y) -> x + y;
        System.out.println(biFunction.apply(4, 5)); //9
        System.out.println(biFunction.andThen(x -> x + 10).apply(4, 5)); //19
    }

二元函数没有compose能力,只是默认实现了andThen。有了一元和二元函数,那么可以通过组合扩展出更多的函数可能。

Function相关扩展接口:

  • BiFunction :R apply(T t, U u);接受两个参数,返回一个值,代表一个二元函数;
  • DoubleFunction :R apply(double value);只处理double类型的一元函数;
  • ToDoubleFunction:double applyAsDouble(T value);返回double的一元函数;
  • ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函数;
  • IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函数;

里面有很多关于int、long、double的一元二元函数,这里就不一一例举了。

Operator

Operator其实就是Function,函数有时候也叫作算子。算子在Java8中接口描述更像是函数的补充,和上面的很多类型映射型函数类似。它包含UnaryOperator和BinaryOperator。分别对应单元算子和二元算子。

UnaryOperator
 @FunctionalInterface
    public interface UnaryOperator<T> extends Function<T, T> {
        static <T> java.util.function.UnaryOperator<T> identity() {
            return t -> t;
        }
    }
  @FunctionalInterface
    public interface BinaryOperator<T> extends BiFunction<T,T,T> {
        public static <T> java.util.function.BinaryOperator<T> minBy(Comparator<? super T> comparator) {
            Objects.requireNonNull(comparator);
            return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
        }
        public static <T> java.util.function.BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
            Objects.requireNonNull(comparator);
            return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
        }
    }

很明显,算子就是一个针对同类型输入输出的一个映射。在此接口下,只需声明一个泛型参数T即可。直接上例子,就非常清楚使用场景了:

  public static void main(String[] args) {
        UnaryOperator<Integer> unaryOperator = x -> x + 10;
        BinaryOperator<Integer> binaryOperator = (x, y) -> x + y;

        System.out.println(unaryOperator.apply(10)); //20
        System.out.println(binaryOperator.apply(5, 10)); //15

        //继续看看BinaryOperator提供的两个静态方法   也挺好用的
        BinaryOperator<Integer> min = BinaryOperator.minBy(Integer::compare);
        BinaryOperator<Integer> max = BinaryOperator.maxBy(Integer::compareTo);
        System.out.println(min.apply(10, 20)); //10
        System.out.println(max.apply(10, 20)); //20
    }

BinaryOperator提供了两个默认的static快捷实现,帮助实现二元函数min(x,y)和max(x,y),使用时注意的是排序器可别传反了:)

提示一个小点:compareTo是Integer的实例方法,而compare是静态方法。其实1.8之后,Interger等都提供了min、max、sum等静态方法

其他的Operator接口:(不解释了)

  • LongUnaryOperator:long applyAsLong(long operand);
  • LongBinaryOperator:long applyAsLong(long left, long right);
  • 。。。省略不写了

最后

我们会发现,JDK的设计还是很有规律的。每个函数式接口对基本数据类型的中的int、long、double都提供了对应的扩展接口。可我们是否想过?为什么别的数据基本类型没有对应接口呢?这个我在Stream那一章有说明原因,各位看官可跳转到那里观看

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/81506090