Java8函数式接口介绍—Consumer、Function、Predicate

何为函数式接口?

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。它通过@FunctionalInterface注解标注,该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
如下是一个函数式接口的简单结构。

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
    
    void doOtherWork(){
    	// Method body
    }
    
     void doOtherWork2(){
    	// Method body
    }
}

函数式接口可以用Lambda表达式来实现其抽象方法,如上面的方法可用Lambda表达式表示如下:

GreetingService greetingService =  msg->System.out.println(msg);
greetingService.sayMessage("hello!");

其实Lambda表达式就是我们自定义实现的sayMessage的具体逻辑,运行后可以得到下面的输出:
在这里插入图片描述
可能你会问这样做有什么好处?先来看看我们以前起一个线程需要怎么做

Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello!");
            }
        });
        thread.start();
    }

而自从将Runnable接口改造成函数接口之后,

new Thread(()->System.out.println("hello")).start();
//拆分一下,又相当于以下
//Runnable runnable = ()->System.out.println("hello!");
//new Thread(runnable).start();

可以看到代码风格变得非常的简洁,只要提供了相应的函数式接口,完全可以替代我们之前的匿名类的写法.

Java8提供的常用函数接口

Consumer

顾名思义,消费接口,它接收T(泛型)类型参数,没有返回值,先来看看它的源码

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

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

accept就是一个抽象函数,具体实现我们可以用Lambda表达式。andThen函数接收一个Consumer,返回值也是一个Consumer,它为我们提供了一个链式的调用,为了更好地理解,下面来看一个例子。

Consumer<Integer> calculate = x->System.out.println(x*2);
calculate.accept(2);

输出

4

链式调用

Consumer<Integer> calculate = x->System.out.println(x*2);
Consumer<Integer> calculate2 = x->System.out.println(x*3);
calculate.andThen(calculate2).accept(2);

输出

4
6

可以看到它链式的执行了我们定义的每一个函数,执行顺序相当于

calculate.accept(2);
calculate2.accept(2);

这个我们查看andThen函数的源码也可以看出来。
consumer的应用,如果你看过List集合的相关源码,你应该可以在顶级接口Iterable中看到它的身影。相关代码如下:

 default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

我们常用的写法

 Arrays.stream(new int[5]).forEach(()->{});

forEach方法对集合中的元素循环处理,而处理的逻辑正在由我们定义的Lambda表达式确定。

Function

上面的Consumer的抽象函数是没有返回值的,而Function与Consumer最大的区别是该接口的抽象函数式有返回值。我们先看源码:

@FunctionalInterface
public interface Function<T, R> {


    R apply(T t);

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


    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;
    }
}

apply

apply接收一个泛型,返回一个泛型,对比Consumer的accept,它相当于增加了返回值。下面列举两个例子

Function<Integer,Integer> calculate = x->x*2;
System.out.println(calculate.apply(2));

输出:

4

compose

compose也是一个链式处理,但对比consumer的链式处理,还是一个是否有返回值的差别,compose的链式处理会将上一次处理的结果作为下一次处理的参数,具体我们看下面的例子

Function<Integer,Integer> calculate = x->x*2;
Function<Integer,Integer> calculate2 = x->x+3;
System.out.println(calculate.compose(calculate2).apply(2));

输出:

10

实际上,上面的完全可以写成calculate.apply(calculate2.apply(2))
结果是一样的,我们再看源码return (V v) -> apply(before.apply(v)),也可以看出compose先调用参数接口的apply,再把执行结果当做参数执行调用者的apply。

andThen

andthen,和compose恰好反过来,它是先执行调用者的apply,在执行参数Function的apply。例子:

 Function<Integer,Integer> calculate = x->x*2;
 Function<Integer,Integer> calculate2 = x->x+3;
 System.out.println(calculate.andThen(calculate2).apply(2));

输出:

7

使用Function我们可以自定义一系列链式处理流程,每个结点的逻辑通过Lambda表达式指定。

Predicate

含义是断言,Predicate又跟Function很像,不过Predicate返回值是固定的boolean

@FunctionalInterface
public interface Predicate<T> {


    boolean test(T t);


    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }


    default Predicate<T> negate() {
        return (t) -> !test(t);
    }


    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

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

test

接收一个泛型参数,返回boolean
判断是否是偶数

   Predicate<Integer> isEven=x->x%2==0;
   System.out.println(isEven.test(5));

输出:

false

至于后面的and,or,negate则分别对应了&&,||,!三个逻辑判断符号,下面写个例子来加深理解

Predicate<Integer> isEven = x->x%2==0;
Predicate<Integer> greater = x->x>5;
Predicate<Integer> equal = x->x==9;
Integer[] number = {1,2,3,4,5,6,7,8,9,10};
List<Integer> list = Arrays.stream(number).filter(isEven.and(greater).or(equal)).collect(Collectors.toList());
list.forEach(x->System.out.println(x));

输出:

6
8
9
10

我们定义了三个断言,然后通过filter进行过滤,上述是过滤出所有大于5的偶数或者等于9的数字。如果我们只想要基数呢?

Arrays.stream(number).filter(isEven.negate().and(greater).or(equal)).collect(Collectors.toList());

只需在isEven后面加上代表!的negate。

好了,以上介绍了三个常用的函数式接口,其实Java提供的函数式接口远不止这些,但是思想基本都差不多,只要理解了,后面遇到其他的也不至于一脸懵逼,感兴趣的朋友可以下来研究研究。

以上为自己学习时的记录,难免有差错,欢迎指正。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值