4、走进函数式编程
在了解了Java 8的一些新特性后,就可以正式开始进入函数式编程了。为了能让大家更快地理解函数式编程,我们先从简单的例子开始。
int[] arrays = {1,2,3,4,5,6,7,8,9};
for(int i: arrays){
System.out.println(i);
}
上述代码循环遍历了数组内的元素,并且进行了数值的打印,这也是传统的做法。如果使用Java 8中的流,那么可以写成这样:
int[] arrays = {1,2,3,4,5,6,7,8,9};
Arrays.stream(arrays).forEach(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
注意: Arrays.stream()方法返回了一个流对象。类似于集合或者数组,流对象也是一个对象的集合,它将给予我们遍历处理流内元素的功能。 这里值得注意的是这个流对象的forEach()方法,它接收一个IntConsumer接口的实现,用于对每个流内的对象进行处理。之所以是IntConsumer接口,因为当前流是IntStream,也就是装有Integer元素的流,因此,它自然需要一个处理Integer元素的接口。函数forEach()会挨个将流内的元素送入IntConsumer进行处理,循环过程被封装在forEach()内部,也就是JDK框架内。 除了IntStream流外,Arrays.stream()还支持DoubleStream、LongStream和普通的对象流Stream,这完全取决于它所接受的参数。 但这样的写法可能还不能让人满意,代码量似乎比原先更多,而且除了引入了不必要的接口和匿名类等复杂性外,似乎也看不出来有什么太大的好处。但是,我们的脚步并未就此打住。试想,既然forEach()函数的参数是可以从上下文中推导出来的,那为什么还要不厌其烦地写出来呢?这些机械的推导工作,就交给编译器去做吧!于是:
int[] arrays = {1,2,3,4,5,6,7,8,9};
Arrays.stream(arrays).forEach((final int x)->{System.out.println(x);});
从上述代码中可以看到,IntStream接口名称被省略了,这里只使用了参数名和一个实现体,看起来简洁很多了。但是还不够,因为参数的类型也是可以推导的。既然是IntConsumer接口,参数自然是int了,于是:
int[] arrays = {1,2,3,4,5,6,7,8,9};
Arrays.stream(arrays).forEach((x)->{System.out.println(x);});
好了,现在连参数类型也省略了,但是这两个花括号特别碍眼。虽然它们对程序没有什么影响,但是为了简单的一句执行语句要加上一对花括号也实属没有必要,那干脆也去掉吧!去掉花括号后,为了清晰起见,把参数申明和接口实现就放在一行吧!
int[] arrays = {1,2,3,4,5,6,7,8,9};
Arrays.stream(arrays).forEach((x)->System.out.println(x););
不过,简化代码的流程并没有结束,在上一节中已经提到,Java 8还支持了方法引用,通过方法引用的推导,你甚至连参数申明和传递都可以省略。
static int[] arr={1,3,4,5,6,7,8,9,10};
public static void main(String[] args) {
Arrays.stream(arr).forEach(System.out::println);
}
至此,欢迎大家正式进入Java 8函数式编程的殿堂,那些看似玄妙的lambda表达式的解析和工作原理已经介绍完毕。 使用lambda表达式不仅可以简化匿名类的编写,与接口的默认方法相结合,还可以使用更顺畅的流式API对各种组件进行更自由的装配。 下面这个例子对集合中所有元素进行两次输出,一次输出到标准错误,一次输出到标准输出中。
public class Lambda2 {
static int[] arrays = {1,2,3,4,5,6,7,8,9};
public static void main(String[] args) {
IntConsumer outPrintln = System.out::println;
IntConsumer errorPrintln = System.err::println;
Arrays.stream(arrays).forEach(outPrint.andThen(errorPrint));
}
}
这里首先使用函数引用,直接定义了两个IntConsumer接口实例,一个指向标准输出,另一个指向标准错误。使用接口默认函数IntConsumer.addThen(),将两个IntConsumer进行组合,得到一个新的IntConsumer,这个新的IntConsumer会依次调用outprintln和errorPrintln,完成对数组中元素的处理。 其中IntConsumer.addThen()的实现如下,仅供大家参考:
default IntConsumer andThen(IntConsumer after) {
Objects.requireNonNull(after);
return (int t) -> { accept(t); after.accept(t); };
}
可以看到,addThen()方法返回一个新的IntConsumer,这个新的IntConsumer会先调用第1个IntConsumer进行处理,接着调用第2个IntConsumer处理,从而实现多个处理器的整合。这种操作手法在Java 8的函数式编程中极其常见,请大家留意。