一、为什么引入函数式接口

作为Java函数式编程爱好者,我们都知道方法引用和 Lambda 表达式都必须被赋值,同时赋值需要类型信息才能使编译器保证类型的正确性。

我们先看一个Lambda代码示例:

x -> x.toString()

我们清楚这里返回类型必须是 String,但 x 是什么类型呢?

Lambda 表达式包含类型推导(编译器会自动推导出类型信息,避免了程序员显式地声明),编译器必须能够以某种方式推导出 x 的类型以生成正确的代码。

同样方法引用也存在此问题,假设你要传递 System.out :: println 到你正在编写的方法 ,你怎么知道传递给方法的参数的类型?

为了解决上述问题,Java 8 引入了函数式接口,在 java.util.function 包,它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型,每个接口只包含一个抽象方法,称为函数式方法。只有确保接口中有且仅有一个抽象方法,Lambda表达式的类型信息才能顺利地进行推导。

二、如何使用函数式接口

在编写接口时,可以使用 @FunctionalInterface 注解强制执行此函数式方法模式:

  1. 在接口上使用注解 @FunctionalInterface ,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

    @FunctionalInterface
    public interface MyFunction {
     /**
      * 自定义的抽象方法
      */
     void run();
    }
  2. 在函数式接口,有且仅有一个抽象方法,Objectpublic方法除外

    @FunctionalInterface
    public interface MyFunction {
     
     /**
      * 自定义的抽象方法
      */
     void run();
     
     /**
      * Object的equals方法
      * @param obj
      * @return
      */
     @Override
     boolean equals(Object obj);
     
     /**
      * Object的toString方法
      * @return
      */
     @Override
     String toString();
     
     /**
      * Object的hashCode方法
      * @return
      */
     @Override
     int hashCode();
     
    }
  3. 在函数式接口中,我们可以使用default修饰符定义默认方法,使用static修饰符定义静态方法

    @FunctionalInterface
    public interface MyFunction {
     
     /**
      * 自定义的抽象方法
      */
     void run();
     
     /**
      * static修饰符定义静态方法
      */
        static void staticRun() {
            System.out.println("接口中的静态方法");
        }
     
        /**
         * default修饰符定义默认方法
         */
        default void defaultRun() {
            System.out.println("接口中的默认方法");
        }
        
    }
  • 为大家演示下自定义无泛型的函数式接口测试实例:

    /**
    * 自定义的无泛型函数式接口
    */
    @FunctionalInterface
    public interface MyFunction {
     
     /**
      * 自定义的抽象方法
      * @param x
      */
     void run(Integer x);
     
        /**
         * default修饰符定义默认方法
         * @param x
         */
        default void defaultMethod(Integer x) {
            System.out.println("接口中的默认方法,接收参数是:" + x);
        }
        
    }

    /**
    * 测试类
    */
    public class MyFunctionTest {

     @Test
     public void functionTest() {
      test(6, (x) -> System.out.println("接口中的抽象run方法,接收参数是:" + x));
     }
     
     public void test(int n, MyFunction function) {
      System.out.println(n);
      function.defaultMethod(n);
      function.run(n);
     }
     
    }

    输出结果:

    6
    接口中的默认方法,接收参数是:6
    接口中的抽象run方法,接收参数是:6
  • 为大家演示下自定义有泛型的函数式接口测试实例:

    /**
     * 自定义的有泛型函数式接口
     */
    @FunctionalInterface
    public interface MyFunctionGeneric<T> {

     /**
      * 转换值
      * @param t
      * @return
      */
     T convertValue(T t);
     
    }

    /**
    * 测试类
    */
    public class MyFunctionGenericTest {

     @Test
     public void convertValueTest() {
      String result = toLowerCase((x) -> x.toLowerCase(), "ABC");
      System.out.println(result);
     }
     
     public String toLowerCase(MyFunctionGeneric<String> functionGeneric, String value) {
      return functionGeneric.convertValue(value);
     }
     
    }

    输出结果:

    abc

    注意:作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口
    的类型。

三、Java8四大内置核心函数式接口

首先总览下四大函数式接口的特点说明:

接口 参数类型 返回类型 方法 说明
Consumer T void void accept(T t) 消费型接口,对类型T参数操作,无返回结果
Supplier - T T get() 供给型接口,创造T类型参数
Function T R R apply(T t) 函数型接口,对类型T参数操作,返回R类型参数
Predicate T boolean boolean test(T t) 断言型接口,对类型T进行条件筛选操作

消费型接口Consumer<T>

java.util.function.Consumer<T> 接口是消费一个数据,其数据类型由泛型决定。

接口源码:

package java.util.function;

import java.util.Objects;

@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); };
    }
}
  1. 抽象方法:void accept(T t),接收并消费一个指定泛型的数据,无需返回结果。
  2. 默认方法:default Consumer<T> andThen(Consumer<? super T> after),如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合
public class ConsumerTest {

 /**
  * 先计算总分,再计算平均分
  */
 @Test
 public void calculate() {
  Integer[] fraction = new Integer[] { 65, 76, 85, 92, 88, 99 };
  consumer(fraction, x -> System.out.println(Arrays.stream(x).mapToInt(Integer::intValue).sum()),
    y -> System.out.println(Arrays.stream(y).mapToInt(Integer::intValue).average().getAsDouble()));
 }
 
 public void consumer(Integer[] fraction, Consumer<Integer[]> x, Consumer<Integer[]> y) {
  x.andThen(y).accept(fraction);
 }
 
}

输出结果:

505
84.16666666666667

由于Consumerdefault方法所带来的嵌套调用(连锁调用),对行为的抽象的函数式编程理念,展示的淋漓尽致。

其他的消费型函数式接口汇总说明:

接口名称 方法名称 方法签名
DoubleConsumer accept (double) -> void
IntConsumer accept (int) -> void
LongConsumer accept (long) -> void
ObjDoubleConsumer accept (T, double) -> void
ObjIntConsumer accept (T, int) -> void
ObjLongConsumer accept (T, long) -> void

供给型接口Supplier<T>

java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() ,用来获取一个泛型参数指定类型的对象数据。

接口源码:

package java.util.function;

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

由于这是一个函数式接口,意味着对应的Lambda表达式需要对外提供一个符合泛型类型的对象数据。

public class SupplierTest {

 public int getMax(Supplier<Integer> supplier) {
  return supplier.get();
 }
 
 /**
  * 获取数组元素最大值
  */
 @Test
 public void getMaxTest() {
  Integer[] data = new Integer[] { 5, 4, 6, 3, 2, 1 };
  int result = getMax(() -> {
   int max = 0;
   for (int i = 0; i < data.length; i++) {
    max = Math.max(max, data[i]);
   }
   return max;
  });
  System.out.println(result);
 }
 
}

其他的供给型函数式接口汇总说明:

接口名称 方法名称 方法签名
BooleanSupplier getAsBoolean () -> boolean
DoubleSupplier getAsDouble () -> double
IntSupplier getAsInt () -> int
LongSupplier getAsLong () -> long

函数型接口Function

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

接口源码:

package java.util.function;

import java.util.Objects;

@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;
    }
}
  1. 抽象方法 apply(T t):该方法接收入参是一个泛型T对象,并返回一个泛型T对象。

  2. 默认方法

    andThen(Function<? super R, ? extends V> after):该方法接受一个行为,并将父方法处理过的结果作为参数再处理。

    compose(Function<? super V, ? extends T> before):该方法正好与andThen相反,它是先自己处理然后将结果作为参数传给父方法执行。

    @Test
    public void andThenAndComposeTest() {
        // 计算公式相同
        Function<Integer, Integer> andThen1 = x -> x + 1;
        Function<Integer, Integer> andThen2 = x -> x * 2;
        Function<Integer, Integer> compose1 = y -> y + 1;
        Function<Integer, Integer> compose2 = y -> y * 2;
        // 注意调用的先后顺序
        // 传入参数2后,先执行andThen1计算,将结果再传入andThen2计算
        System.out.println(andThen1.andThen(andThen2).apply(2));
        // 传入参数2后,先执行compose2计算,将结果再传入compose1计算
        System.out.println(compose1.compose(compose2).apply(2));
    }

    输出结果:

    6
    5
  3. 静态方法identity():获取到一个输入参数和返回结果一样的Function实例。

来一个自驾九寨沟的代码示例:

public class FunctionTest {
 
 @Test
 public void findByFunctionTest() {
  Function<BigDecimal, BigDecimal> getMoney = m -> m.add(new BigDecimal(1000));
  BigDecimal totalCost = getMoney.apply(new BigDecimal(500));
  System.out.println("张三的钱包原本只有500元,自驾川西得去银行再取1000元,取钱后张三钱包总共有" +           Function.identity().apply(totalCost) + "元");
  BigDecimal surplus = cost(totalCost, (m) -> {
   System.out.println("第二天出发前发现油不足,加油前有" + m + "元");
   BigDecimal lubricate = m.subtract(new BigDecimal(300));
   System.out.println("加油300后还剩余" + lubricate + "元");
   return lubricate;
  }, (m) -> {
   System.out.println("到达景区门口,买景区票前有" + m + "元");
   BigDecimal tickets = m.subtract(new BigDecimal(290));
   System.out.println("买景区票290后还剩余" + tickets + "元");
   return tickets;
  });
  System.out.println("最后张三返程到家还剩余" + surplus + "元");
 }

 public BigDecimal cost(BigDecimal money, Function<BigDecimal, BigDecimal> lubricateCost,
   Function<BigDecimal, BigDecimal> ticketsCost) {
  Function<BigDecimal, BigDecimal> firstNight = (m) -> {
   System.out.println("第一晚在成都住宿前有" + m + "元");
   BigDecimal first = m.subtract(new BigDecimal(200));
   System.out.println("交完200住宿费还剩余" + first + "元");
   return first;
  };
  Function<BigDecimal, BigDecimal> secondNight = (m) -> {
   System.out.println("第二晚在九寨县住宿前有" + m + "元");
   BigDecimal second = m.subtract(new BigDecimal(200));
   System.out.println("交完200住宿费还剩余" + second + "元");
   return second;
  };
  return lubricateCost.andThen(ticketsCost).andThen(secondNight).compose(firstNight).apply(money);
 }

}

输出结果:

张三的钱包原本只有500元,自驾川西得去银行再取1000元,取钱后张三钱包总共有1500元
第一晚在成都住宿前有1500元
交完200住宿费还剩余1300元
第二天出发前发现油不足,加油前有1300元
加油300后还剩余1000元
到达景区门口,买景区票前有1000元
买景区票290后还剩余710元
第二晚在九寨县住宿前有710元
交完200住宿费还剩余510元
最后张三返程到家还剩余510元

其他的函数型函数式接口汇总说明:

接口名称 方法名称 方法签名
BiFunction apply (T, U) -> R
DoubleFunction apply (double) -> R
DoubleToIntFunction applyAsInt (double) -> int
DoubleToLongFunction applyAsLong (double) -> long
IntFunction apply (int) -> R
IntToDoubleFunction applyAsDouble (int) -> double
IntToLongFunction applyAsLong (int) -> long
LongFunction apply (long) -> R
LongToDoubleFunction applyAsDouble (long) -> double
LongToIntFunction applyAsInt (long) -> int
ToDoubleFunction applyAsDouble (T) -> double
ToDoubleBiFunction applyAsDouble (T, U) -> double
ToIntFunction applyAsInt (T) -> int
ToIntBiFunction applyAsInt (T, U) -> int
ToLongFunction applyAsLong (T) -> long
ToLongBiFunction applyAsLong (T, U) -> long

断言型接口Predicate<T>

java.util.function.Predicate<T> 接口中包含一个抽象方法: boolean test(T t) ,用于条件判断的场景。默认方法:and or nagte (取反)。

接口源码:

package java.util.function;

import java.util.Objects;

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

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用逻辑连接起来实现并且的效果时,类始于 Consumer接口 andThen()函数 其他三个雷同。

public class PredicateTest {
 /**
  * 查找在渝北的Jack
  */
 @Test
 public void findByPredicateTest() {
  List<User> list = Lists.newArrayList(new User("Johnson", "渝北"), new User("Tom", "渝中"), new User("Jack", "渝北"));
  getNameAndAddress(list, (x) -> x.getAddress().equals("渝北"), (x) -> x.getName().equals("Jack"));
 }
 
 public void getNameAndAddress(List<User> users, Predicate<User> name, Predicate<User> address) {
  users.stream().filter(user -> name.and(address).test(user)).forEach(user -> System.out.println(user.toString()));
 }
}

输出结果:

User [name=Jack, address=渝北]

其他的断言型函数式接口汇总说明:

接口名称 方法名称 方法签名
BiPredicate test (T, U) -> boolean
DoublePredicate test (double) -> boolean
IntPredicate test (int) -> boolean
LongPredicate test (long) -> boolean

四、总结

Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进,因为这允许你编写更简洁明了,易于理解的代码。

Java8新特性探索之函数式接口的更多相关文章

  1. java8新特性学习:函数式接口

    本文概要 什么是函数式接口? 如何定义函数式接口? 常用的函数式接口 函数式接口语法注意事项 总结 1. 什么是函数式接口? 函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口 ...

  2. Java8新特性探索之Stream接口

    一.为什么引入Stream流 流是一系列与特定存储机制无关的元素--实际上,流并没有"存储"之说.使用流,无需迭代集合中的元素,就可以从管道提取和操作元素.这些管道通常被组合在一起 ...

  3. java8新特性 - 什么是函数式接口 @FunctionalInterface?

    什么是函数式接口 @FunctionalInterface 源码定义 /** * An informative annotation type used to indicate that an int ...

  4. Java JDK1.8新特性之四大函数式接口

    JDK 1.8的一些新特性 四大核心函数式接口(Consumer.Predicate.Supplier.Function),结合lambda表达式 import java.util.ArrayList ...

  5. 乐字节-Java8核心特性实战之函数式接口

    什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的.从Java8开始引入了函数式接口,其说明比较简单:函数式接口(Functional Interface)就是一个有且仅有一 ...

  6. Java8新特性第2章(接口默认方法)

    在Java中一个接口一旦发布就已经被定型,除非我们能够一次性的更新所有该接口的实现,否者在接口的添加新方法将会破坏现有接口的实现.默认方法就是为了解决这一问题的,这样接口在发布之后依然能够继续演化. ...

  7. 01 语言基础+高级:1-10 JDK8新特性_day12【函数式接口】

    day12[函数式接口] 主要内容自定义函数式接口函数式编程常用函数式接口 教学目标能够使用@FunctionalInterface注解能够自定义无参无返回函数式接口能够自定义有参有返回函数式接口能够 ...

  8. JAVA 8 主要新特性 ----------------(四)Lambda函数式接口

    一.什么是函数式接口 只包含一个抽象方法的接口,称为函数式接口.  你可以通过 Lambda 表达式来创建该接口的对象.(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法 ...

  9. Java8新特性探索之Lambda表达式

    为什么引入Lambda表达式? Lambda 表达式产生函数,而不是类. 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 lamb ...

随机推荐

  1. AI小白必读:深度学习、迁移学习、强化学习别再傻傻分不清

    摘要:诸多关于人工智能的流行词汇萦绕在我们耳边,比如深度学习 (Deep Learning).强化学习 (Reinforcement Learning).迁移学习 (Transfer Learning ...

  2. Emit动态生成代理类用于监控对象的字段修改

    利用Emit动态生成代理对象监控对象哪些字段被修改,被修改为什么值 被Register的对象要监控的值必须是Virtual虚类型 必须使用CreateInstance创建对象 必须使用DynamicP ...

  3. VS2017 Xamarin开发Android时首次部署完成后直接闪退

    项目属性切换到Android选项,在打包属性上有一个[使用共享运行时]的选项要取消勾选,默认打钩时apk文件比较小,但程序无法运行起来. 取消后安装包一小变成几十M,这个目前好像没什么好的解决办法,毕 ...

  4. Centos-分割文件-split

    split 分割文件,将一个文件分割为多个 相关选项 -b 指定文件大小,可以在size后面添加单位后缀,b表示512字节,k表示1KB,m表示MB -n 指定分割文件的长度,默认为1000行 -d ...

  5. 安装zabbix3.0以及升级到5.0过程

    关闭防火墙: systemctl stop firewalld.service systemctl disable firewalld.service 需要关闭 selinux,一定要关闭这个,开启s ...

  6. Winsock 编程详解

    转载请注明出处!本文地址:https://www.cnblogs.com/teternity/p/WinSock.html Winsock 编程 目录 通用函数讲解 WSAStartup WSACle ...

  7. 使用AirtestProject+pytest做支付宝小程序UI自动化测试

    一,前言 1,背景 因公司业务需要做支付宝小程序的UI自动化测试,于是在网上查找小程序的自动化资料,发现微信小程序是有自己的测试框架的,但几乎找不到支付宝小程序UI自动化测试相关的资料.白piao失败 ...

  8. 多测师讲解接口测试 _理论基础知识001_高级讲师肖sir

    前言: 我们今天进入接口测试的学习! 今天学习的内容是偏向理论 接口理论 了解接口测试(1) 一.什么是接口测试? 接口统称api,即程序与程序之间的对接.交接.交互.是测试系统组件间接口的一种测试. ...

  9. 【C++学习笔记】C++经典十二道笔试题!你能做出几道?

    1. 运行下面的C++代码,得到的结果是什么? #include "stdafx.h" #include<iostream> using namespace std; ...

  10. 【暑假集训】HZOI2019 水站 多种解法

    题目内容 已知有一个\(n\)层的水站: \(W_i\)表示未操作之前第\(i\)层的已有水量: \(L_i\)表示第\(i\)个水站能够维持或者储存的水的重量: 表示在第\(P_i\)层进行减压放水 ...