Java8新特性:Lambda表达式与函数式编程
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
前言
Java8已经发布好久了说是新特性其实早就不算新特性了之前一直想补上这块内容现在来讲讲我对Lambda表达式和函数式编程的理解。
正文
接口作为方法参数
对于大多数Java初学者来说当第一次遇到接口作为方法参数这样的语法时是非常困惑的而且这种语法非常常见比如我们创建线程时Thread类的其中一个构造函数竟然以Runnable接口为参数。
public Thread(Runnable target) {
//...
}
一直以来Java都被认为是一种纯面向对象的语言“万物皆对象”的思想已经深入人心了。但是随着Java8的发布函数式编程的思想越来越明显尤其是Lambda表达式和Stream流的引入让Java焕发了新的活力它允许人们可以用函数式编程思维来思考问题。
关键是这种方式有什么好处呢官方讲的很专业但是讲的太专业了不太好理解。我尝试从个人理解来发表下看法函数式编程有点像模板模式函数主体规定了整体流程变化的部分就在于接口类型的参数在整体流程中的参与度。以上面的Thread类为例外层的Thread类规定好了想要实例化一个Thread线程对象的具体流程而变化的是线程具体所做的工作那么这一部分就被封装在入参的接口类型Runnable中那么我们每次在新建Thread类时重点关注变化的部分即可也即重点实现Runnable接口提供的run()方法。
匿名内部类
Lambda表达式出现之前函数式编程使用内名内部类来实现还是以上面的Thread类为例由于是以接口类型为形参那么实际运行时要传入一个实际对象匿名内部类就是实例化了一个对象传给了形参这个对象重点实现了接口的抽象方法。
new Thread(new Runnable() {
public void run() {
System.out.println("hi");
}
}).start();
Thread类的入参接受一个Runnable的具体实现类对象并实现了run()方法也就实现了线程的具体功能。但是我们仔细来看入参这段代码实际上我们关心的只有run()方法里面的方法内容其他的都是冗余信息为了简化语法于是引入了Lambda表达式。
new Runnable() { // 这一行实际不需要
public void run() { // 这一行也不需要
System.out.println("hi"); // 真正需要保留的只有这一行
}
}
Lambda表达式
Lambda表达式作为一种语法糖是为了简化匿名内部类大家千万不要认为其有多高深熟练掌握使用方法即可。上面我们说过只需要关注接口中的方法那么Lambda表达式将冗余的信息都去掉了只保留了方法的参数列表和方法体。特别需要说明的是只有接口中有且仅有一个抽象方法才能使用Lambda表达式。
new Thread(() -> {
System.out.println("hi");
});
初学者第一次看到这样的语法就很无语开始我也是很抗拒认为不如匿名内部类清晰其实慢慢习惯后就能接受了。
Lambda表达式只保留了接口方法的参数列表和方法体
1、) 小括号就是参数列表没有入参就是空括号有参数可以写成(ab)这种形式
2、-> 箭头是固定形式连接参数列表和方法体
3、{} 花括号就是方法体是真正实现抽象方法的地方如果方法体只有一行花括号可以省略return关键字也可以省略。
再举个例子列表排序Arrays.sort方法参数列表接收一个Comparator接口实现自定义的排序方式。
String[] array = new String[]{"a", "b", "c"};
Arrays.sort(array, (a, b) -> a.compareTo(b));
看到了吗Lambda表达式可以将匿名内部类简化成只有一行。Comparator接口中的compare方法有2个入参那么我们可以指定参数列表ab参数的名字叫什么都可以随便取。方法体的实现只有一行那么花括号和return都可以省略。这样简洁的语法糖谁不爱
函数式编程接口
我们上面说过了只要一个接口有且仅有一个抽象方法就可以使用Lambda表达式。于是Java专门弄了一个注解@FunctionalInterface把它加到接口上一方面起到注释的作用让人看了就知道这是个函数式接口另一方面告诉编译器要保障接口中仅有一个抽象方法。
我们可以自定义函数式接口这里我就不再举例了。Java8给我们提供了一些写好的函数式接口适用于不同的场景这些接口在java.util.function包里。我们来看一些常见的。
Supplier接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
顾名思义是一个供应接口接口及抽象方法比较简单用来得到一个泛型指定类型的对象。比如接口指定了泛型为String那么get方法返回的只能是String类型。get方法非常简单意味着可发挥的空间也越大举个例子
public static void main(String[] args) {
int ret = getMax(() -> Math.max(1, 2)); // get方法的Lambda表达式
System.out.println(ret);
}
public static int getMax(Supplier<Integer> supplier) {
return supplier.get();
}
Consumer接口
它和Supplier接口正好相反它不生成数据而是消费一个数据具体消费什么类型的数据取决于泛型指定的类型。只列出了简单的accept方法
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
来举个例子看看是怎么消费数据的
public static void main(String[] args) {
consume("jimmy", (a) -> System.out.println("hello" + a)); // accept方法的lambda表达式
}
public static void consume(String message, Consumer<String> comsumer) {
comsumer.accept(message);
}
Predicate接口
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
专门用来做推断具体推断什么类型是由泛型来指定的。举个例子
public static void main(String[] args) {
boolean ret = predicate(12, (a) -> a <= 10);
System.out.println(ret);
}
public static boolean predicate(Integer a, Predicate<Integer> predicate) {
return predicate.test(a);
}
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);
}
可以根据一个类型T得到一个类型R下面举一个类型转换的例子
public static void main(String[] args) {
exchange("123", (a) -> Integer.parseInt(a));
}
public static int exchange(String text, Function<String, Integer> function) {
return function.apply(text);
}
总结
函数式编程在框架类代码中很常见尤其是Spring源码中特别多它的最主要作用就是简化代码封装变化。后面我们在讲Spring源码时会再次讲到函数式编程。