Java8函数式编程之函数式接口

Java8函数框架解读

函数编程的最直接的表现,莫过于将函数作为数据自由传递,结合泛型推导能力,使代码表达能力获得飞一般的提升。那么,Java8是怎么支持函数编程的呢?主要有三个核心概念:

  • 函数接口(Function)
  • 流(Stream)
  • 聚合器(Collector)

在上一篇的转载博客< 深入浅出 Java 8 Lambda 表达式>里,了解了什么是Lambda表达式,什么是函数式接口,Lambda表达式的一般语法

但是我还是不太懂怎么自己开发自己的函数式接口以及怎么调用。今天在阅读Arrays源码时看到setAll方法,我明白怎么用Lambda表达式以及怎么自定义自己的函数式接口

怎么用Lambda表达式

首先setAll方法源码

public static void setAll(double[] array, IntToDoubleFunction generator) {
        Objects.requireNonNull(generator);//这一行不用管,和本片的内容没有联系
        for (int i = 0; i < array.length; i++)
            array[i] = generator.applyAsDouble(i);
    }

IntToDoubleFunction 是函数式接口源码如下,函数接口必须用注解@FunctionalInterface注解,函数式接口中有且仅有一个函数,不能没有也不能多余一个函数;这个接口的作用就是根据输入的int型value值返回一个double值。

@FunctionalInterface
public interface IntToDoubleFunction {
    double applyAsDouble(int value);
}

下面是setAll方法的使用

double[] dou = new double[3];
Arrays.setAll(dou,(int i)->i*3);
System.out.println(Arrays.toString(dou))

输出

[0.0, 3.0, 6.0]

在< 深入浅出 Java 8 Lambda 表达式>里说:在 Java 中,Lambda 表达式是对象,他们必须依附于一类特别的对象类型——函数式接口(functional interface)。所以这里就可以理解为setAll的Lambda表达式就是接口IntToDoubleFunction 的一个实现类并返回了这个对象,有点像匿名内部类。按照这样的思想,setAll方法还是可以这样调用:

double[] dou = new double[3];
IntToDoubleFunction obj = new IntToDoubleFunction (){
    @Override
     public double applyAsDouble(int value) {
           return (double)(value*3);
     }
};
Arrays.setAll(dou,obj);
System.out.println(Arrays.toString(dou))

输出

[0.0, 3.0, 6.0]

结果一样,说明之前的是对的。Lambda表达式有点像匿名内部类,简化的匿名内部类。
需要注意的是Lambda表达式不同于匿名内部类,匿名内部类在编译的时候会生成相应的class字节码,但是功能相同,但是不会生成相应的class字节码;而是一个函数,反编译上面含有Lambda表达式的class字节码得到

  private static double lambda$main$0(int);
    descriptor: (I)D
    Code:
       0: iload_0
       1: iconst_3
       2: imul
       3: i2d
       4: dreturn
}

是一个静态函数,所以Lambda表达式和匿名内部类还是有区别,但是可以按照匿名内部类理解。

怎么自定义函数式接口

假如我现在要把输入的int数据转换成String,并赋给String[]数组。
首先定义函数式接口

@FunctionalInterface
interface InToString{
    String inToString(int a);
}

定义类似setAll的方法

class ABC{
    public void test(String[]a,InToString obj){
        for(int i = 0; i < a.length; i++){
            a[i] = obj.inToString(i);
        }
    }
}

调用上面方法

public static void main(String[] args) {
    String[] dou = new String[3];
    ABC.test(dou,(int i)->Integer.toString(i));
    System.out.println(Arrays.toString(dou))
}

输出

[0, 1, 2]

以上就是函数式编程之函数式接口与Lambda表达式的使用。

函数接口

关于函数接口,需要记住的就是两件事:

  • 函数接口是行为的抽象;
  • 函数接口是数据转换器。

最直接的支持就是 java.util.Function 包。定义了四个最基础的函数接口:

  • Supplier < T >: 数据提供器,可以提供 T 类型对象;无参的构造器,提供了 get 方法;
  • Function < T,R >:数据转换器,接收一个 T 类型的对象,返回一个 R类型的对象; 单参数单返回值的行为接口;提供了 apply, compose,andThen, identity 方法;
  • Consumer < T >: 数据消费器, 接收一个 T类型的对象,无返回值,通常用于设置T对象的值; 单参数无返回值的行为接口;提供了 accept, andThen 方法;
  • Predicate< T >: 条件测试器,接收一个 T 类型的对象,返回布尔值,通常用于传递条件函数; 单参数布尔值的条件性接口。提供了 test (条件测试) , and-or- negate(与或非) 方法。
    其中, compose, andThen, and, or, negate 用来组合函数接口而得到更强大的函数接口。

其它的函数接口都是通过这四个扩展而来。

  • 在参数个数上扩展: 比如接收双参数的,有 Bi 前缀, 比如 BiConsumer< T,U >, BiFunction< T,U,R > ;
  • 在类型上扩展: 比如接收原子类型参数的,有 [Int|Double|Long][Function|Consumer|Supplier|Predicate]
  • 特殊常用的变形: 比如 BinaryOperator , 是同类型的双参数 BiFunction< T,T,T > ,二元操作符 ; UnaryOperator 是 Function< T,T > 一元操作符。

函数接口可以接收哪些值呢?

  • 类/对象的静态方法引用、实例方法引用。引用符号为双冒号 ::
  • 类的构造器引用,比如 Class::new
  • lambda表达式

你可能感兴趣的:(java)