vlambda博客
学习文章列表

看完必会Java函数式编程(一)

我们知道,Java 是面向对象的编程语言,我们对抽象的概念肯定不陌生,面向对象编程就是对数据进行抽象。Java 8 之后出现了 lambda 表达式,让我们可以用 Java 实现函数式编程,即对行为抽象。

什么是函数式编程?

首先我们来简单了解一下 Java 的 lambda 表达式的用法,看下面的代码:

// 1. 无参无返回值Runnable noArgsNoRet = () -> System.out.println("Hello Lambda");// 2. 有参无返回值Consumer<String> oneArgsNoRet = str -> System.out.println(str);// 3. 无参有方法体Supplier<String> noArgsHasRet = () -> { String str = "a" + "b"; return str;};// 4. 有两个参数,有返回值,将方法体的计算结果作为返回值返回BinaryOperator<Integer> add = (x, y) -> x + y;// 5. 注明类型的参数Consumer<String> oneArgs = (String str) -> System.out.println(str);

仅当接口只有一个抽象方法时,才可以使用 lambda 表达式。我们知道,Runnable接口只有一个run()方法,所以我们可以用 lambda 表达式作为行为传递给Runnable接口,告诉它你的run()方法将使用我传递的行为,并将生成的对象实例赋值给noArgsNoRet

其他的几条语句也是一样的概念,我们可以先不用管他们都是什么接口,接下来看一下 lambda 表达式本身。每一个 lambda 表达式都会使用一个->符号,作为分隔符,左边代表方法的参数,右边代表方法体。只有一个参数可以省略括号();只有一条语句的方法体可以省略花括号{}。所以:

1.无参数和多个参数必须用括号2.参数注明类型必须用括号3.多条语句必须用花括号

所以怎么理解 Java 的函数式编程?我们先来参考一下 js 语言,在 js 的函数式编程中,有一个特点,即:函数是一等公民。意思是,函数跟其它数据类型一样处于平等地位,可以赋值给其他变量,也可以作为参数传入另一个函数。

Java 的概念略有不同,但是可以有助于我们理解。

那么,Java 将函数赋值给变量了吗?

光看上面的例子代码,好像是这样的噢,一个=号,像是将右边的函数赋值给了左边的变量。

但是!不要忘了,Java 是面向对象的语言,变量只会被赋值为对象或基本类型,很显然函数不可能被赋值给变量!

所以说,Java只是将函数这个行为,传递给了抽象接口的方法,告诉这个抽象接口,你的方法要用我的这个函数实现!

这就是所谓的对行为抽象。

java.util.function 包

理解了 Java 如何对行为进行抽象,并使用 lambda 表达式实现,我们就可以来看一下 java.util.function包了。我发现网上绝大部分对这个包的讲解都很少,为什么要有这个包?里面的接口是干什么用的?

在这个包里面,全部都是函数式接口,都使用了@FunctionalInterface注解进行声明。我们直接来看一下里面的接口,找名字就叫函数的Function接口,它的关键的代码如下:

@FunctionalInterfacepublic interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t); }

@FunctionalInterface注解声明这个包是函数式接口,编译的时候会判断接口里面是不是只有一个抽象方法,如果不是会报错。

接口声明了两个泛型,分别用在抽象方法R apply(T t)上。表示接受一个类型的参数,返回一个类型的返回值。普通的使用方法很简单:

public class LambdaTest2 { public static void main(String[] args) { // 1. 声明函数对象实例 // 接收一个Integer类型的参数,返回String类型的返回值 Function<Integer, String> function = i -> i + ""; // 2. 使用传递给函数的方法体 String str = function.apply(61); System.out.println(str); }}

相当于给你提供了一个开箱即用的函数式接口,但是这么使用体现不了函数式接口的优势,我们要对行为抽象,看下面这个例子:

public class LambdaTest2 { public static void main(String[] args) { FunctionTest<Integer, String> ft = new FunctionTest<>(); ft.func(i -> i * 2 + "", 61); }}class FunctionTest<T, R> { // 将行为进行抽象,在你需要操作的时候再定义 public void func(Function<T, R> function, T t) { R r = function.apply(t); System.out.println(r); }}

这个例子没有实际意义,但是表达了对行为进行抽象的特征,将Function函数接口作为参数,调用方法时再对行为进行具体定义,也可以理解为对代码进行传递。

在最有用的操作容器的Stream类,你会看到非常多的这种对行为进行抽象。因篇幅原因,Stream将会在下一篇文章进行讲解。

分类

java.util.function包的接口非常多,有40多个,每一个接口声明了不同的参数和返回值,我们怎么记得住这么多?

其实他们是有规律的,根据名称来进行分类,有如下几个大类:

1.Consumer(消费):接受参数,无返回值2.Function(函数):接受参数,有返回值3.Operator(操作):接受参数,返回与参数同类型的值4.Predicate(断言):接受参数,返回boolean类型5.Supplier(供应):无参数,有返回值

比如说Consumer接口和BiConsumer接口,他们分别接受一个参数和两个参数,但共同点是都没有返回值,都是消费者。其他同理,完整的接口列表如下:

接口名 描述
Consumer 接受一个参数,无返回值
BiConsumer 接受两个参数,无返回值
DoubleConsumer 接受一个double类型的参数,无返回值
IntConsumer 接受一个int类型的参数,无返回值
LongConsumer 接受一个long类型的参数,无返回值
ObjDoubleConsumer 接受一个自定义类型的参数和一个double类型的参数,无返回值
ObjIntConsumer 接受一个自定义类型的参数和一个int类型的参数,无返回值
ObjLongConsumer 接受一个自定义类型的参数和一个long类型的参数,无返回值
Function 接受一个参数,有返回值
BiFunction 接受两个参数,有返回值
DoubleFunction 接受一个double类型的参数,有返回值
IntFunction 接受一个int类型的参数,有返回值
LongFunction 接受一个long类型的参数,有返回值
IntToDoubleFunction 接受一个int类型的参数,返回一个double类型的值
IntToLongFunction 接受一个int类型的参数,返回一个long类型的值
LongToDoubleFunction 接受一个long类型的参数,返回一个double类型的值
LongToIntFunction 接受一个long类型的参数,返回一个int类型的值
DoubleToIntFunction 接受一个double类型的参数,返回一个int类型的值
DoubleToLongFunction 接受一个double类型的参数,返回一个long类型的值
ToDoubleBiFunction 接受两个参数,返回一个double类型的值
ToDoubleFunction 接受一个参数,返回一个double类型的值
ToIntBiFunction 接受两个参数,返回一个int类型的值
ToIntFunction 接受一个参数,返回一个int类型的值
ToLongBiFunction 接受两个参数,返回一个long类型的值
ToLongFunction 接受一个参数,返回一个long类型的值
BinaryOperator 接受两个相同类型的参数,返回一个相同类型的值
DoubleBinaryOperator 接受两个double类型的参数,返回一个double类型的值
DoubleUnaryOperator 接受一个double类型的参数,返回一个double类型的值
IntBinaryOperator 接受两个int类型的参数,返回一个int类型的值
IntUnaryOperator 接受一个int类型的参数,返回一个int类型的值
LongBinaryOperator 接受两个long类型的参数,返回一个long类型的值
LongUnaryOperator 接受一个long类型的参数,返回一个long类型的值
UnaryOperator 接受一个参数,返回一个相同类型的值
Predicate 接受一个参数,返回一个boolean类型的值
BiPredicate 接受两个参数,返回一个boolean类型的值
DoublePredicate 接受一个double类型的参数,返回一个boolean类型的值
IntPredicate 接受一个int类型的参数,返回一个boolean类型的值
LongPredicate 接受一个long类型的参数,返回一个boolean类型的值
Supplier 无参数,有返回值
BooleanSupplier 无参数,返回一个boolean类型的值
DoubleSupplier 无参数,返回一个double类型的值
IntSupplier 无参数,返回一个int类型的值
LongSupplier 无参数,返回一个long类型的值

总结

了解了这些,我们基本就掌握了 Java 函数式编程的基础了,在这些基础上,咱们平常就可以方便地编写函数式接口的实现代码了。