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