看完必会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 函数式编程的基础了,在这些基础上,咱们平常就可以方便地编写函数式接口的实现代码了。
