前言
JDK8在Java开发届声望颇高,甚至有人称 “版本任你发,我用Java8”,这足以表明JDK8对于Java的重要性,作为Java开发的我们当然有必要认真学习并掌握JDK8的特性。在接下来的系列文章里面我会和大家一起学习JDK8的新特性。
1. 函数式接口
定义: 函数式接口是 只包含一个抽象方法的接口
我们可以通过@FunctionalInterface
注解来表示一个接口符合函数式接口的定义
大家都知道接口里面的是抽象方法,生来就是被实现的,函数式接口除了常规的
实现方式之外,还有特殊的实现方式,也就是下面要讲的Lambda
表达式
看到这里无法理解函数式接口存在的意义不要紧,下面结合Lambda
表达式写一个简单的示例就能很好地理解了。
2. Lambda表达式
Lambda在其他语言中也有体现,这里不做过多介绍,我们直击主题,什么是Lambda表达式?
Lambda是函数的另一种写法,Lambda表达式的出现让Java语言风格更紧凑
下面看看Lambda表达式的一个基础示例
public class Main {
public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1,2,3,4,5,6);
nums.forEach(num->{
System.out.println("当前遍历的数字:"+num);
});
}
}
在这个示例里面,forEach
方法的入参就是一个Lambda表达式。等等?说好的Lambda表达式是一个函数呢,怎么成了一个方法的参数了。
这里就不得不说Lambda表达式与函数式接口的关系了。
在这里我们可以看到forEach
的入参类型为Consumer<? super T>
,那么接下来看看这个Consumer
是个什么东西。
突然就看到了之前提过的@FunctionalInterface
注解,Lambda表达式 作为函数式接口类型的入参,意思就是Lambda表达式是对象,它的类型就是函数式接口。
看到这里,学过C语言的小伙伴有没有很熟悉的感觉,C语言中的函数指针,把A方法作为参数传给B方法,在B方法里面可以调用A方法来处理一些逻辑,这样就做到了方法的动态调用。
而函数式接口的唯一一个抽象方法,就是定描述这个所谓的“函数指针”的定义,就是规定这个函数应该长什么样,入参是啥,返回值是啥。
接下来做一个简单的实例,我们使用函数式接口实现给分数评级的操作
首先定义一个评级的函数式接口,这个函数式接口定义了 “评级” 这个行为
@FunctionalInterface
public interface MarkInterface {
// 定义打分行为,传入分数,返回等级枚举
ScoreLevelEnum mark(Double score);
}
接下来在Main函数里面测试一下
public class FuncInterfaceMain {
public static void main(String[] args) {
// 定义具体的行为,如何计算等级
// 类型就是我自己的MarkInterface,等号右边就是lambda表达式
MarkInterface markOption = num->{
if (num >= 90d){
return ScoreLevelEnum.A;
}else if(num >= 80d){
return ScoreLevelEnum.B;
}else if (num >= 70d){
return ScoreLevelEnum.C;
}else if (num >= 60d){
return ScoreLevelEnum.D;
}else{
return ScoreLevelEnum.E;
}
};
// 现在来调用这个Lambda表达式对象,实现计算等级
ScoreLevelEnum scoreLevel = markOption.mark(89.8);
System.out.println("89.8分的成绩,等级是"+scoreLevel.getName());
}
}
函数式接口和Lambda表达式小结:Lambda表达式是函数的一种写法,但是其本质是对象,所以它的类型需要 函数式接口 来承载。函数式接口定义了实现其唯一方法的Lambda
表达式的函数定义。
额外补充:
Java内置了许多函数式接口,定义了许多常用的行为,我们在使用的时候就不用自己再定义函数式接口了,非常方面。内置的函数式接口如下:
函数式接口 | 说明 |
---|---|
Consumer<T> | 入参类型是T ,返回值类型是void ,方法定义为void accept(T t) |
Supplier<T> | 入参类型是void ,返回值类型是T ,方法定义为T get() |
Function<T, R> | 入参是T ,返回值类型是R ,表示传入T ,得到R ,方法定义为R apply(T t) |
Predicate<T> | 入参是T ,返回值是boolean ,表示传入一个对象,得到boolean 值方法定义为 boolean test(T t); |
这4个是比较常用的函数式接口,基本可以涵盖日常开发大部分的场景,非常方便。
3. 方法引用
先说结论:方法引用就是Lambda表达式!
众所周知,Lambda表达式是函数式接口的对象,是可以作为参数传递给方法的。上一个例子里面,我们是自己写了一个Lambda表达式,那如果我想把一个现有的方法赋值给函数式接口怎么办?
使用方法引用!
接下来使用一个简单示例来看看如何使用,我们这里来感受一下把Lambda表达式作为参数传递
public class MethodRefEntrance {
@Test
public void entrance(){
Scanner in = new Scanner(System.in);
System.out.println("请输入你的分数:");
double score = in.nextDouble();
// 准备打分策略,这就是方法引用的形式
MarkInterface markOperate = MethodRefEntrance::markStrategy;
// 调用评级方法
markScore(score, markOperate);
}
/**
* 对分数评级的方法
* @param score 分数
* @return 分数等级枚举
*/
public static ScoreLevelEnum markStrategy(Double score){
// 评级策略
if (score >= 90d){
return ScoreLevelEnum.A;
}else if(score >= 75d){
return ScoreLevelEnum.B;
}else if (score >= 60d){
return ScoreLevelEnum.C;
}else{
return ScoreLevelEnum.D;
}
}
// 通过传入评级策略实现多策略评级
public static void markScore(Double score,MarkInterface markOperate){
System.out.println("分数是:"+score);
final ScoreLevelEnum level = markOperate.mark(score);
System.out.println("你的分数评级是:"+level.getName());
}
}
首先这里定义了public void entrance()
方法作为测试的入口,我们不关心它。接下来我定义了两个方法:
public static ScoreLevelEnum markStrategy(Double score)
public static void markScore(Double score,MarkInterface markOperate)
这两个方法,markScore
方法定义了给分数评级的操作,markStrategy
方法是按照函数式接口里面定义的方法形式来写的。
// 准备打分策略
MarkInterface markOperate = MethodRefEntrance::markStrategy;
// 准备打分策略,这就是方法引用的形式
markScore(score, markOperate);
这两行代码中,两个::
这种奇怪的写法就是方法引用的语法。
方法引用的基本语法如下:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
为什么要出现这样的形式,有什么好处吗?大家对于策略模式应该还是有所了解的,那么这个写法和策略模式像不像呢,是不是觉得这个是策略模式的青春版。
如果接到需求,说要改变打分策略,那么我就不用在原来写好的方法里面修修改改,而是直接写一个新的方法,然后通过方法引用赋值给MarkInterface
变量。
就像这样MarkInterface markOperate = 新方法
,这样设计是不是很符合开闭原则,由此我们可以拓展出许多的场景。
这里又有一个问题,构造函数能引用不,语法是不是MyClass::MyClass
。
答案是能引用构造函数,但是语法是MyClass::new
再也不用羡慕隔壁C/C++的函数指针啦,咱们Java的更安全,更好用。
总结
本篇文章带大家了解了Lambda表达式和函数式接口的语法以及如何使用,我后续会更新更多的关于JDK8特性的文章,文章中涉及的源代码的Gitee地址:features-demo-jdk8。
觉得我写得不错的话记得关注一下,我们下篇文章见~