JDK8新特性系列——函数式接口与Lambda表达式


前言

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
觉得我写得不错的话记得关注一下,我们下篇文章见~

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值