小编
专注分享Java技术,坚持原创。你的关注和转发就是我们最好的动力。
前言
java8为我们引入的Lambda表达式、Stream ApI以及方法引用,它们为了java提供函数式编程的支持,虽然目前JDK已经出现到14的版本了,但是小编在工作中遇到一些员工中,对java的函数式编程并不有所了解,或者是不晓其原理。
今天我们就以一个对0~10间的累计为例子,从面向过程的编程方式开始,层层递进,演进到我们现在的函数式编程。
面向过程的累计
private static int sumInJava5(int size) { int sum = 0; // range方法是自定义的,获取1到10的集合 List<Integer> values = range(1,size); for (Integer i : values) { sum += i; } return sum;}
这个例子使用到了jdk1.5的foreach语法,但其本身也是面向过程的,我们可以看出,加的操作是一步步的按照流程走,有先后之分。并不是通过调用对象的方法去进行累计。
说到这里,讲解一个疑惑,很多人只会用foreach语法,但是不知道集合和数组可以去使用foreach。其实是实现了Iterable的接口,提供了遍历的功能。
面向对象的累计
private static int sumInJava5Oop(int size, Sum sum) { int result = 0; // range方法是自定义的,获取1到10的集合 List<Integer> values = range(1,size); for (Integer value : values) { result = sum.apply(result, value); } return result;}
private interface Sum { /** * 加法运算 * @param a 左值 * @param b 右值 * @return 返回值 */ int apply(int a, int b);}
private static class SumImpl implements Sum{ @Override public int apply(int a, int b) { return a + b; }}
这个累加是通过一个Sum类型的对象。调用它的apply方法实现的,是一个面向对象的过程。
在使用这个计算方法过程中,无论是通过传入实现类还是匿名内部类,代码是很繁琐的,jdk的开发者们为了解决这个问题,引入了Lambda表达式、Stream ApI以及方法引用。
使用传统的方式与方法引用调用我们的方法
// 这是传统的sum = sumInJava5Oop(10, new SumImpl());// 这是方法引用sum = sumInJava5Oop(10, SumImpl::apply);
我们的第二行代码就是我们方法引用形式,使用到的操作符“::”,这个操作符把方法引用分成两边,左边是类名或者某个对象的引用,右边是方法名。
值得注意的是:由于类名引用需要方法是静态的,因此,java8提供了接口可以有静态方法的特性,在此之前的版本是没有的。
此外使用方法引用有一个重要的特点,虽然它作为我们方法的参数,但是并不需要是相同的类型和相同的方法名,只要保证相同的签名就行(方法返回值和方法的参数),如下面几种方式:
// 可以是实例对象的引用sum = sumInJava5Oop(10, new SumImpl()::apply);// 可以是完全不同类型对象方法名字sum = sumInJava5Oop(10, OopDemo::calculate);
方法引用的格式
方法引用有以下四种类型
这些都是java8提供新的编程语法支持的,它只要让编译器知道我们是方法参数、返回值以及在方法里面需要做什么就行。看到这里相信有不少人引发了另一个特性,没错就是我们的lambda表达式。
lambda表达式调用我们的累计方法
因为方法引用仅是一个方法实现的应用,在没有lambda表达式表达式的时候,我们选要在其他的地方编写我们的方法,而有了lambda表达式表达式以后,只需要在调用的位置写表达式就行:
sumInJava5Oop(10, (a,b) -> a+b);
注意:lambda表达式的主体仅包含一个表达式,且该表达式仅调用了一个已经存在的方法。方法引用的通用特性,方法引用所使用方法的入参和返回值与lambda表达式实现的函数式接口的入参和返回值一致。lambda可以看作是方法的实现。
函数式接口
我们可以发现,如果我们的Sum接口存在多个抽象方法,那么就不能用过lambda和方法引用进行传参操作。这是新语法提供高灵敏度的同时,也提供与之相应的保障。显然这种接口比较特殊,又有一个新的概念——函数式接口。
函数式接口的定义:
创建函数式接口注意事项:
函数式接口可以有多个方法
虽然函数式接口的定义要求了我们一个接口有且只有一个抽象方法。但是java有提供接口的默认方法实现(default-method)来解决这个问题。
@FunctionalInterfaceprivate interface Sum { // 抽象方法 int apply(int a, int b); // default-method default int xx(int a, int b){ return a*b; }; // Object的方法 String toString();}
尽管Sum接口有3个方法,但是方法引用和lambda表达式走的还是我们的抽象方法。
使用default-method注意事项