今天来讲讲Java8对于函数式编程的支持,同时也是Java8版本新特性之一的lambda表达式。
在我自己的理解中,我认为要想快速理解lambda的关键很简单,只要记住一句话,lambda表达式其实不是什么新东西,他的本质就是匿名内部类,仅此而已,只是编译器对匿名内部类的创建做了简化处理,只要我们书写更为简洁的lambda语句即可,有关这一点可以使用eclipse的反编译工具对生成的class文件进行处理,应该就会明白我们所写的lambda语句被反编译后其实就是一个个匿名内部类。
下面我就来顺着思路说说lambda表达式
首先,我们回顾下Java怎样创建线程,可以继承Thread类、实现Runnable接口或者使用线程池来实现。
下面我使用实现Runnable接口来创建一个线程
1 | class MyThread implements Runnable { |
输出:
Thread-0
线程被成功创建,并且打印出了当前线程名称。
那通过我的上一篇文章《Java函数式系列-闭包和内部类》的学习,我们知道接口Runnable可以通过匿名内部类的方式直接被new出来,这样子就省去了MyThread类的创建
1 | public class Main { |
代码相比上面少了点吧,但是我觉得还是不够爽!因为Runnable接口只有一个抽象方法run,我们要实现这个接口势必要重写这个方法,而且new Runnable()这个过程完全可以通过Thread类的构造函数推断出来,所以lambda表达式来了
这段代码重新用lambda写一遍是这种场景
1 | public class Main { |
直接变一行代码了!
So,Lambda 表达式是创建匿名内部类的语法糖。在编译器的帮助下,可以让开发人员用更少的代码来完成工作。
函数式接口
在对上述代码进行简化时,我们定义了两个前提条件。第一个前提是要求接口类型,如示例中的 Runnable,可以从当前上下文中推断出来;第二个前提是要求接口中只有一个抽象方法。如果一个接口仅有一个抽象方法(除了来自 Object 的方法之外),它被称为函数式接口(functional interface)。函数式接口的特别之处在于其实例可以通过 Lambda 表达式或方法引用来创建。Java 8 的 java.util.function 包中添加了很多新的函数式接口。如果一个接口被设计为函数式接口,应该添加@FunctionalInterface 注解。编译器会确保该接口确实是函数式接口。当尝试往该接口中添加新的方法时,编译器会报错。
这会体会到lambda的酸爽了吧,还不够,那我再搞几个操作吧
lambda表达式遍历字符串数组
1 | import java.util.Arrays; |
我们来看看forEach的源码
1 | void forEach(Consumer<? super T> action); |
跟进一下Consumer类
1 |
|
如果之前的都看懂的话,到这里就很容易明白forEach是如何实现的了,不就是利用jdk提供的函数式接口来帮助我们完成这个lambda表达式嘛。
Java8提供了四大核心函数式接口Function、Consumer、Supplier、Predicate
告诉你怎么记,Function的翻译为函数,那么它提供的抽象方法就是既有入参也有出参,Consumer叫消费者,只懂消费,不懂生产,所以他只有入参,Supplier叫提供者,所以只有出参,Predicate译为断言,所以他有入参,出参是booleen类型的
Function<T, R>
T:入参类型,R:出参类型
调用方法:R apply(T t);
定义函数示例:Function<Integer, Integer> func = p -> p * 10; // 输出入参的10倍
调用函数示例:func.apply(10); // 结果100
Consumer
T:入参类型;没有出参
调用方法:void accept(T t);
定义函数示例:Consumer
consumer= p -> System.out.println(p); // 因为没有出参,常用于打印、发送短信等消费动作 调用函数示例:consumer.accept(“18800008888”);
Supplier
T:出参类型;没有入参
调用方法:T get();
定义函数示例:Supplier
supplier= () -> 100; // 常用于业务“有条件运行”时,符合条件再调用获取结果的应用场景;运行结果须提前定义,但不运行。 调用函数示例:supplier.get();
Predicate
T:入参类型;出参类型是Boolean
调用方法:boolean test(T t);
定义函数示例:Predicate
predicate = p -> p % 2 == 0; // 判断是否、是不是偶数 调用函数示例:predicate.test(100); // 运行结果true
又来新操作了,看好,不知道你有没有感觉上述的lambda遍历数组还是有点优化的余地,哪里呢?
就是这一句
Arrays.asList(name).stream().forEach((x) -> System.out.println(x));
lambda的参数我们定义的x,既然只有一个参数我们为什么不可以再省略呢?直接让编译器帮我们推断出来不就好了?
还真有这操作,Java8的另一个新特性,双冒号“::”操作符
我们看看简化后的样子
1 | import java.util.Arrays; |
我们可以通过“::”关键字配合函数式接口来访问类的构造方法,对象方法,静态方法,学会了吧。
目前只懂这么多了,完毕!