二函数式接口全面剖析

Posted Java码农社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二函数式接口全面剖析相关的知识,希望对你有一定的参考价值。

函数式接口全面剖析

一、概述

1、是什么

函数式接口是一个被@FunctionalInterface注解所修饰且接口内只有一个抽象方法。严格意义上讲接口内只有一个抽象方法就是一个函数式接口,而注解只是强制声明一下,带上注解就是必须只能有一个抽象方法,若不符合则编译报错。起到一个限制的作用。

2、有毛用

配合lambda表达式和stream,简直神器。

3、答疑

上一篇我用Runnable和Comparator两个接口做了举例,我还留下个疑问:为什么自己写的接口不行,这两个接口却可以完美结合lambda?看源码可知,这两个类完全符合函数式接口的特征(被@FunctionalInterface修饰且只有一个抽象方法,若没被注解修饰,但是只有一个抽象方法,这样也是可以的)。

二、自定义函数式接口

1、函数式接口

/**
* 有且只有一个抽象方法才可声明函数式接口
* @FunctionalInterface注解只是起到一个限制的作用,加上注解后若不符合函数式接口规范会编译报错。
*/
@FunctionalInterface
public interface AppleFilter {
   boolean filter(Apple apple);
}

2、实体类

@Data
@AllArgsConstructor
public class Apple {
   /** 颜色 */
   private String color;
   /** 重量 */
   private long weight;
}

3、需求

找到颜色为红色的苹果。

4、码前分析

首先我们自定义的函数式接口是一个有一参且有返回格式的。所以lambda可写成如下:(Apple apple) -> {逻辑处理}因为需求是找到红色的,所以代码体仅一行,可去掉{}apple -> "red".equals(apple.getColor);其中apple为函数式接口的参数,省略了类型和(),第一篇讲过,一参可省略(),不知道的回去再看一遍。

4、开始编码

public static void main(String[] args) {
   // 初始化苹果集合
   List<Apple> list = Arrays.asList(new Apple("green", 10), new Apple("red", 20), new Apple("green", 30));
   // 最终的结果
   List<Apple> result = new ArrayList<>();
   // 声明我们自己的函数式接口的规则,若不用lambda则是如下这个丑样子:
   /*
    * AppleFilter filter = new AppleFilter() {
    *     @Override
    *     public boolean filter(Apple apple) {
    *         return "red".equals(apple.getColor());
    *     }
    * };
    *
    * 而lambda简单一句话。param -> body
    * 箭头左侧apple代表filter接口的入参,是Apple类型的,这里省略了类型,写全的话是Apple apple。
    * 而箭头右侧则是接口的规则,资深的人一眼就发现了,这是策略模式的一种写法。的确如此。
    */
   AppleFilter filter = apple -> "red".equals(apple.getColor());
   for (Apple apple : list) {
       // filter.filter(apple)则是验证apple是否是上述规则(红色),因为filter返回的boolean
       if (filter.filter(apple)) {
           result.add(apple);
       }
   }
   System.out.println(result);
}

看到这里大家可能还会觉得lambda还是没啥用。就是匿名内部类创建的时候容易了,一句话代替了四五句。不错,目前为止能悟到这个就行了。

三、Java8内置函数式接口

1、内置

2、核心接口

函数式接口 参数类型 返回类型 描述
Predicate<T>断定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回boolean的值。boolean test(T t);
Consumer<T>消费型接口 T void 对类型为T的对象进行操作void accept(T t);
Supplier<T>供给型接口 T 返回类型为T的对象。T get();
Function<T,R>函数型接口,最强大的一个 T R 对类型为T的对象进行操作,并返回结果,结果是R类型的对象。R apply(T t);

3、核心接口的扩展

上面四大核心接口都有N个扩展(可以假装看成是具体的实现。)具体有如下这几类:拿Function函数式接口举例

// 多参数的
java.util.function.BiFunction<T, U, R> {}
// 具体类型的:double
java.util.function.DoubleFunction<R> {}
// 具体类型的:int
java.util.function.IntFunction<R> {}
// 具体类型的:long
java.util.function.LongFunction<R> {}

其他三大核心接口也拥有如上那些具体的函数式接口,很简单的,可以自己去看源码。都在java.util.function包下。

四、实战演示

1、Predicate断定型接口

1.1、需求

找到颜色为绿色的苹果

1.2、实现

private static List<Apple> findPredicate(List<Apple> list, Predicate<Apple> predicate) {
   List<Apple> result = new ArrayList<>();
   for (Apple apple : list) {
       // 利用Predicate的test方法进行断定,具体业务需要看调用方,是个策略模式
       if (predicate.test(apple)) {
           result.add(apple);
       }
   }
   return result;
}


public static void main(String[] args) {
   List<Apple> list = Arrays.asList(new Apple("green", 10), new Apple("red", 20), new Apple("green", 30));
   /*
    * 第二个参数是Predicate的test(Apple)方法:
    * 由于只有一个参数,所以可以省略小括号
    */
   List<Apple> appleList = findPredicate(list, apple -> apple.getColor().equals("green"));
   System.out.println(appleList);
}

1.3、LongPredicate

1.3.1、需求

找到重量为30的苹果

1.3.2、实现
private static List<Apple> findLongPredicate(List<Apple> list, LongPredicate predicate) {
   List<Apple> result = new ArrayList<>();
   for (Apple apple : list) {
       // 利用Predicate的test方法进行断定,具体业务需要看调用方,是个策略模式
       if (predicate.test(apple.getWeight())) {
           result.add(apple);
       }
   }
   return result;
}

public static void main(String[] args) {
   List<Apple> list = Arrays.asList(new Apple("green", 10), new Apple("red", 20), new Apple("green", 30));
   List<Apple> appleList = findLongPredicate(list, weight -> weight == 30);
   System.out.println(appleList);
}

1.4、BiPredicate

1.4.1、需求

找到颜色为绿色且重量为30的苹果

1.4.2、实现
private static List<Apple> findBiPredicate(List<Apple> list, BiPredicate<String, Long> predicate) {
   List<Apple> result = new ArrayList<>();
   for (Apple apple : list) {
       // 利用Predicate的test方法进行断定,具体业务需要看调用方,是个策略模式
       if (predicate.test(apple.getColor(), apple.getWeight())) {
           result.add(apple);
       }
   }
   return result;
}

public static void main(String[] args) {
   List<Apple> list = Arrays.asList(new Apple("green", 10), new Apple("red", 20), new Apple("green", 30));
   /*
    * 由于只有一个参数,所以不可以省略小括号
    */
   List<Apple> appleList = findBiPredicate(list, (color, weight) -> color.equals("green") && weight == 30);
   System.out.println(appleList);
}

2、Consumer消费型接口

2.1、需求

利用Consumer写一个公用的apple处理方法(策略模式)。

2.2、实现

private static void findConsumer(List<Apple> list, Consumer<Apple> consumer) {
   for (Apple apple : list) {
       consumer.accept(apple);
   }
}

public static void main(String[] args) {
   List<Apple> list = Arrays.asList(new Apple("green", 10), new Apple("red", 20), new Apple("green", 30));
   findConsumer(list, apple -> System.out.println(apple));
}

3、Supplier供给型接口

3.1、需求

创建一个apple对象并返回

3.2、实现

private static Apple findSupplier(Supplier<Apple> supplier) {
   return supplier.get();
}

public static void main(String[] args) {
   /*
    * T get();接口无参有返回。
    * 所以:
    * () -> ...
    */
   Apple apple = findSupplier(() -> new Apple("red", 111L));
   System.out.println(apple);
}

4、Function函数型接口

4.1、需求

创建一个apple对象并返回对象的toString()

4.2、实现

private static String findFunction(Apple apple, Function<Apple, String> function) {
   return function.apply(apple);
}


public static void main(String[] args) {
   String s = findFunction(new Apple("red", 1234L), apple -> apple.toString());
   System.out.println(s);
}

5、补充

认认真真看demo,自己动手!!!其他的比如BiFunction,LongSupplier等等都自己写吧,举一反三足够了!写到这你会发现比以前更复杂,还不如不用!没毛病,但是这只是开始,我写大量的demo只是为了让你明白有这几种函数式接口的存在,到底什么是函数式接口,到底怎么用。你把这块搞懂了,以后stream的时候你会很顺畅,而且会发现功能无比强大。

五、方法推导

1、是什么

当要传递给lambda体的操作,已经有了实现好的方法可以使用方法进行引用。方法引用:使用操作符“::”,将方法名和对象或类的名字分隔开来。

2、四种方式

  • 对象::实例方法

  • 类::静态方法

  • 类::实例方法

  • 类::new

3、实战

3.1、对象::实例方法

public static void main(String[] args) {
   Apple apple = new Apple("black", 123L);
   /**
    * ::隔开,左侧是对象,右侧是对象的实例方法
    */
   Supplier<String> supplier = apple::getColor;
   System.out.println(supplier.get());
   // 再比如
   PrintStream out = System.out;
   Consumer<String> consumer = out::println;
   consumer.accept("hello");
}

3.2、类::静态方法

public static void main(String[] args) {
   // 输入两个int,返回int.
   BiFunction<Integer, Integer, Integer> comparator = Math::max;
   // 返回2
   System.out.println(comparator.apply(1,2));
}

3.3、类::实例方法

public static void main(String[] args) {
   BiFunction<String, Object, Boolean> function = String::equals;
   System.out.println(function.apply("1", "1"));
}

3.4、类::new

public static void main(String[] args) {
   // 无参的 等于new String()
   Consumer<String> consumer = String::new;
   // 有参的 等于new Apple("black", 111L);
   BiFunction<String, Long, Apple> appleBiFunction = Apple::new;
   Apple black = appleBiFunction.apply("black", 111L);
   System.out.println(black);
}

六、总结

目前为止你应该学会如下:

  • 箭头函数怎么用

  • 函数式接口常用的几种

  • 方法推导

  • 没觉得好,没关系!后面会觉得好。

七、再次理解

案例:

private static List<Apple> findPredicate(List<Apple> list, Predicate<Apple> predicate) {
   List<Apple> result = new ArrayList<>();
   for (Apple apple : list) {
       // 利用Predicate的test方法进行断定,具体业务需要看调用方,是个策略模式
       if (predicate.test(apple)) {
           result.add(apple);
       }
   }
   return result;
}


public static void main(String[] args) {
   List<Apple> list = Arrays.asList(new Apple("green", 10), new Apple("red", 20), new Apple("green", 30));
   /*
    * 第二个参数是Predicate的test(Apple)方法:
    * 由于只有一个参数,所以可以省略小括号
    */
   List<Apple> appleList = findPredicate(list, apple -> apple.getColor().equals("green"));
   System.out.println(appleList);
}

分析:findPredicate()有两个参数,第一个list没毛病,第二个他要一个Predicate接口的引用,可是我们却传进去一段代码,这是什么鬼?看如下:

if (predicate.test(apple)) {
}

我们利用类进行test(apple),换成我们传进去的代码就是:

if (apple.getColor().equals("green")) {
}

为什么呢?因为test参数apple就是我们当前这个apple,所以最终也就长这个样子。

八、广告

https://gitee.com/geekerdream/

以上是关于二函数式接口全面剖析的主要内容,如果未能解决你的问题,请参考以下文章

二函数式接口

[二] java8 函数式接口详解 函数接口详解 lambda表达式 匿名函数 方法引用使用含义 函数式接口实例 如何定义函数式接口

全面剖析Smarty缓存机制二[清除缓存方法]

Java学习笔记3.9.2 Lambda表达式 - 函数式接口

Java8新特性及实战视频教程完整版

java8新特性学习二(函数式functional接口)