轻松理解Lambda的作用及使用

想要理解Lambda的使用,我们先要弄清楚什么是函数式接口和函数式编程。
函数式接口:有且仅有一个抽象方法的接口
函数式编程:把运算过程写成嵌套函数调用
总之,函数式编程就是,当我想实现一个功能时,我只想写这个功能的实现过程,即一个函数,其他的我都不想管。这个听起来容易,但是实际操作起来,却往往要多写很多无关紧要时代码。我们现在理解不了没关系,我们先看一个例子。我们模拟一个老板叫一个员工干活的场景:
先有一个员工接口(假设员工此时还没找到,所以只有接口,我们将来还要招聘员工):

public interface Emploee {
    void doSomething();
}

然后我们还有一个boss类,boss可以在主方法中调用ask方法叫员工去干活:

public class Boss {

    public static void main(String[] args) {
    }

    public static void ask(Emploee emploee){
        emploee.doSomething();
    }
}

现在,boss想叫员工去倒杯茶,该怎么实现呢?我们应该先招聘一个员工,再让他学会倒茶这项技能,然后他就能给boss倒茶了。所以我们先实现接口并重写doSomething方法:

public class RealEmploee implements Emploee{
    @Override
    public void doSomething() {
        System.out.println("给我倒杯茶!");
    }
}

然后我们在boss类的主函数中实例化该类,并调用doSomething方法:

public class Boss {

    public static void main(String[] args) {
        Emploee emploee = new RealEmploee();
        ask(emploee);
    }

    public static void ask(Emploee emploee){
        emploee.doSomething();
    }
}

然后我们就能在控制台上看到输出的给我倒杯茶!了。我们看到,为了实现小小一个功能,我们写了很多代码。其实,我们可以用匿名内部类进行优化,我们就不直接实现接口了。

public class Boss {

    public static void main(String[] args) {
        ask(new Emploee() {
            @Override
            public void doSomething() {
                System.out.println("给我倒杯茶!");
            }
        });
    }

    public static void ask(Emploee emploee){
        emploee.doSomething();
    }
}

其实,这个时候代码看起来已经好多了,我们想要完成倒茶这个操作我们只增加了以下代码:

        ask(new Emploee() {
            @Override
            public void doSomething() {
                System.out.println("给我倒杯茶!");
            }
        });

但是,这并不是简约的。如果boss再叫员工去买烟、又或者去拿快递,我们就要再写以下代码。

        ask(new Emploee() {
            @Override
            public void doSomething() {
                System.out.println("给我去买烟!");
            }
        });
        ask(new Emploee() {
            @Override
            public void doSomething() {
                System.out.println("给我去拿快递!");
            }
        });

我们发现,有大量的代码重复,真正在改变的,是doSomething函数的实现。
以下代码都是重复的:

        ask(new Emploee() {
            @Override
            public void doSomething() {
            }
        });

所以,为了解决这个问题,Lambda来了。用Lambda表达改造后,代码变成了这样:

public class Boss {

    public static void main(String[] args) {
        ask(()->{
            System.out.println("给我倒杯茶!");
        });
    }

    public static void ask(Emploee emploee){
        emploee.doSomething();
    }
}

初看Lambda表达式也许会很懵逼
我们来看一下Lambda表达式的标准格式:
1. 一些参数
2. 一个箭头
3. 一段代码

格式:

  • (参数列表) -> {一些重写方法的代码};

解释说明格式:

  • ():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
  • ->:传递的意思,把参数传递给方法体{}
  • {}:重写接口的抽象方法的方法体

所以,我们把最重要的部分抽取了出来。我们的最终目的是想要实现函数的功能,现在,我们实现了,这就行了,我们就不去实现接口,重写方法了。当然,在底层,这些都是会实现的,但是,不再需要我们自己写了,这一切都交给编译器,我们只关注函数本身。现在我们再看看函数式编程的定义:
函数式编程:把运算过程写成嵌套函数调用
所以说,在Java中,函数式编程体现就是Lambda。
在这里插入图片描述
这样的话,是不是以后我们想要实现接口的抽象方法都可以这样写呢?当然不是,Lambda的使用是有条件的。

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法,即文章开头所说的函数式接口。
    想象一下,如果接口中有多个抽象方法,编译器怎么知道我们想要实现哪一个?我们再看一下lambda的格式:(参数列表) -> {一些重写方法的代码};,我们有指明重写哪一个方法吗?显然没有,所以为了避免找不到方法的情况,要求接口中有且仅有一个抽象方法
  2. 使用Lambda必须具有上下文推断
    也就是方法的参数类型必须为Lambda所要求的接口类型,才能使用Lambda作为该接口的实例。
        public static void ask(Emploee emploee){
         emploee.doSomething();
     }
    
    Emploee便是函数式接口。

注意事项:
假设boss想叫员工导5杯茶,初学者可能会写出这样的代码:

public class Boss {

    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            ask(()->{
                System.out.println("给我第" + i + "倒杯茶!");
            });
        }

    }

    public static void ask(Emploee emploee){
        emploee.doSomething();
    }
}

但是我们发现编译错误了
在这里插入图片描述
我们翻译一下编译错误信息:lambda表达式中使用的变量应该是final或者有效的final。也就是说,lambda表达式中使用的局部变量应该是final修饰的量或者不会再改变的量。
其实,这个约束本来是针对匿名内部类的使用的,由于lambda表达式是由匿名内部类演变而来,所以对lambda表达式也有同样的约束。
解决方法:

  1. 将变量定义为全局变量:
public class Boss {

    static int i;
    
    public static void main(String[] args) {
        for (i = 1; i <= 5; i++) {
            ask(()->{
                System.out.println("给我第" + i + "倒杯茶!");
            });
        }

    }

    public static void ask(Emploee emploee){
        emploee.doSomething();
    }
}

  1. 给变量加final关键字:
public class Boss {

    public static void main(String[] args) {
        for (int j = 1; j <= 5; j++) {
            final int i = j;
            ask(()->{
                System.out.println("给我第" + i + "倒杯茶!");
            });
        }

    }

    public static void ask(Emploee emploee){
        emploee.doSomething();
    }
}

注意,其实即使我们在这里不加final关键字,也还是能编译通过并成功运行,因为Java 8 之后,在匿名类或 Lambda 表达式中访问的局部变量,如果不是 final 类型的话,编译器自动加上 final 修饰符。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值