Java基础——十一、Lambda

19 篇文章 0 订阅
12 篇文章 0 订阅

十一、Lambda

是什么?

1.定义

Lambda表达式是Java 8引入的一种新语法,用于简化代码,尤其是在处理函数式接口(即只有一个抽象方法的接口)时,它允许你更简洁地表示可以作为参数传递的一段代码,比如匿名内部类的替代品。

Lambda表达式是什么?

image-20220806131922149

  1. 希腊字母表中排序第十一位的子母,英语名为Lambda
  2. C/C++语言是有函数指针的,函数指针可以作为一个参数传给一个方法,而java是没有这个特性的。
  3. 为了解决这个问题,Lambda表达式就出来了!
  4. Lambda表达式的核心就是函数式接口
  5. 质属于函数式变成的概念。

什么是函数式接口(Functional Interface)?

函数式接口定义:任何接口,如果只包含唯一一个抽象方法,那它就是一个函数式接口!

public interface Runnable{
    public abstract void run();
}

对于函数式接口,可以通过Lambda表达式来创建改接口的对象

Runnable接口也是函数式接口哦!

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

可以根据自己的需求实际应用到多线程中。

为什么要用?

为什么要用它?

  1. 避免匿名内部类定义过多
  2. 代码更加简洁
  3. 去掉了一些无意义代码,留下核心逻辑

怎么用?

1.语法
(parameters) -> expression
或
(parameters) -> { statements; }
// 无参数的Lambda表达式
Runnable r1 = () -> System.out.println("Hello, Lambda!");

// 有一个参数的Lambda表达式
Consumer<String> c1 = (s) -> System.out.println(s);

// 有多个参数的Lambda表达式
BinaryOperator<Integer> add = (a, b) -> a + b;
2.推导

不用它的时候是怎么实现的?

Lambda表达式推导代码

//1.定义一个函数式接口
interface ILikes{
    void lambda();
}
//2.接口实现类
class Likes implements ILike{
    @Override
    public void lambda() {
        System.out.println("I like lambda!");
    }
}
public class LambdaTest2 {
    //3.静态内部类
    static class Likes2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("I like lambda2!");
        }
    }
    //4.执行main方法
    public static void main(String[] args) {

        //2.接口实现类
        ILike like = new Likes();
        like.lambda();

        //3.静态内部类
        like = new Likes2();
        like.lambda();

        //4.局部内部类
        class Likes3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("I like lambda3!");
            }
        }
        like = new Likes3();
        like.lambda();

        //5.匿名内部类,没有类的名称,必须借助接口或者父类
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda4!");
            }
        };
        like.lambda();

        //6.用Lambda简化
        like = () -> {
            System.out.println("I like lambda5!");
        };
        like.lambda();
    }
}

传入变量的时候怎么用?

//1.定义一个函数式接口
interface ILikes1{
    void lambda(int a);
}
//2.接口实现类
class Likes1 implements ILikes1{
    @Override
    public void lambda(int a) {
        System.out.println("I like lambda!");
    }
}
public class LambdaTest3 {
    public static void main(String[] args) {
        //3.lambda表达式简化
        ILikes1 like = new Likes1();
        like = (a) -> {
            System.out.println("I like lambda 1!");
        };
        like.lambda(11);
        
        //4.lambda表达式再简化(注:只有一个变量且只有一行执行语句才可如此简化!)
        like = a -> System.out.println("I like lambda 2!");
    }
}
3.具体使用
(1).替代匿名内部类

Lambda表达式最常见的用途是替代函数式接口的匿名内部类,例如在处理集合时常用的ComparatorRunnable接口。

    /**
     * 定义了一个名为comparator的传统匿名内部类比较器,用于比较两个整数大小。
     * 使用Lambda表达式定义了另一个名为filter的比较器,功能与comparator相同。
     * 创建变量compare存储filter比较1和2的结果。
     * 打印compare的值(-1,表示1小于2)。
     */
    private static void test1(){
        Comparator<Integer> comparator = new Comparator<Integer>(){
            /**
             * 实现Comparator接口的compare方法,用于比较两个Integer对象的大小。
             *
             * @param o1 第一个Integer对象,需要参与比较
             * @param o2 第二个Integer对象,需要参与比较
             * @return 返回两个整数比较的结果,负数表示o1小于o2,零表示两者相等,正数表示o1大于o2
             */
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };

        // 使用Lambda表达式创建一个Comparator比较器实例
        // 该比较器用于比较两个Integer对象的大小
        Comparator<Integer> filter = (o1, o2) -> o1.compareTo(o2);
        // 使用比较器的compare方法比较两个整数的大小
        // 这里比较的是整数1和整数2
        int compare = filter.compare(1, 2);
        // 打印比较的结果
        // 结果为负数表示1小于2,零表示它们相等,正数表示1大于2
        System.out.println(compare);
    }
(2).Stream API中的使用

Lambda表达式与Stream API结合使用时非常强大,能够让你用一种声明式的风格处理集合数据。

    /**
     * 测试Stream API的示例方法
     * 该方法展示了如何使用Stream API进行过滤操作
     * 特别是演示了如何过滤出以特定字母开头的名字
     */
    private static void test2() {
        // 初始化一个名字列表
        List<String> names = Arrays.asList("Tom", "Jerry", "Mike", "Tiger");

        // 使用Stream API进行过滤和遍历
        // 以下代码注释详细解释了Stream API的使用步骤
        names.stream() // 转换列表为Stream,以便使用Stream API
                // 过滤出以'T'开头的名字
                .filter(name -> name.startsWith("T"))
                //.forEach(name -> System.out.println(name)); // 以下是一种替代写法,输出每个过滤后的名字
                .forEach(System.out::println);
    }
(3).事件处理

在GUI编程中(如Swing),Lambda表达式也常用于简化事件处理代码。

button.addActionListener(event -> System.out.println("Button clicked!"));
4.工作应用
  1. 简化代码,提高可读性:在实际开发中,Lambda表达式可以极大地减少代码量,特别是在处理集合、事件、异步任务等场景下,使代码更加简洁、清晰。
  2. 与函数式编程结合:Java 8以后,Java逐渐引入了函数式编程的理念,Lambda表达式正是这一变化的核心部分。通过Lambda表达式,可以更容易地使用函数式接口、链式调用等功能,进一步提升代码的可维护性和可扩展性。
  3. 性能优化:Lambda表达式在一些场景下还能优化性能。通过使用并行流(parallelStream()),可以充分利用多核CPU的性能,显著提高数据处理速度。
5.总结
  • Lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就要用代码块包裹。
  • 前提是接口为函数式接口。
  • 多个参数也可以省略参数类型,要去掉的都去掉,但必须加上括号。

底层实现

1.函数式接口和FunctionalInterface

Lambda表达式必须依赖于函数式接口。Java8引入了@FunctionalInterface注解,用于显示声明某个接口为函数式接口。函数式接口只能包含一个抽象方法。

举例说明

@FunctionalInterface
public interface MyFunctionalInterface {
    void execute();
}
2. 生成字节码

Lambda表达式在编译时并不会直接生成匿名内部类的字节码,而是通过invokedynamic指令在运行时动态生成,这使得Lambda表达式相比匿名内部类在性能上有一定优势。

3.底层实现原理

Lambda表达式通过java.lang.invoke.MethodHandles.Lookup类的findStatic方法和MethodHandle类的invokeExact方法来绑定具体的方法引用。具体来说,Lambda表达式被编译为一个invokedynamic指令,该指令通过java.lang.invoke.LambdaMetafactory来动态生成所需的函数式接口实例。

// 示例:Lambda表达式编译后的字节码
public static void main(String[] args) {
    Runnable r = () -> System.out.println("Hello, Lambda!");
    r.run();
}

编译后的字节码中,Lambda表达式会被转换为invokedynamic指令,而具体的执行逻辑则在运行时由LambdaMetafactory生成。

4.核心源码

了解Java中Lambda表达式的底层实现,有助于理解其背后的高效执行机制。Java通过引入invokedynamic指令和LambdaMetafactory类来支持Lambda表达式。以下是核心实现过程的介绍和关键代码示例。

1. Lambda表达式的基本语法
List<String> list = Arrays.asList("a", "b", "c");

// Lambda表达式用于遍历列表
list.forEach(item -> System.out.println(item));
2. 编译器的处理

在编译过程中,Lambda表达式不会像匿名类那样被编译成单独的类文件。相反,编译器生成一个调用invokedynamic指令的字节码,这条指令会在运行时动态解析和执行Lambda表达式。

list.forEach(item -> System.out.println(item));

这段代码经过编译后,会生成类似以下字节码指令:

invokedynamic #1:accept:(Ljava/lang/Object;)V
3. invokedynamicLambdaMetafactory
  • invokedynamic 指令invokedynamic是Java 7引入的一种字节码指令,旨在支持动态类型语言的特性。在Java 8中,invokedynamic被用来支持Lambda表达式的动态调用。

  • LambdaMetafactory:在Lambda表达式的运行时,invokedynamic指令会调用LambdaMetafactory.metafactory方法,该方法负责生成一个调用Lambda表达式的实例。这一过程在第一次调用时完成,并且生成的实例会被缓存,以便后续调用能更高效地执行。

核心代码示例:

public class LambdaExample {
    public static void main(String[] args) {
        Runnable r = () -> System.out.println("Hello, Lambda!");

        // 编译后,这段代码会被转换为以下方式:
        // Invokedynamic指令调用LambdaMetafactory.metafactory
        r.run();
    }
}
4. LambdaMetafactory 工作原理

LambdaMetafactory.metafactory的核心任务是创建一个CallSite对象,该对象包含了Lambda表达式的目标方法句柄。在运行时,JVM会根据这个句柄创建一个函数式接口的实例(如RunnableCallable等),然后执行Lambda表达式。

public class LambdaMetafactory {
    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType) {
        // 核心实现略
    }
}
  • MethodHandle:是一种对方法的引用,可以直接调用底层的方法。它比反射更加高效。
  • CallSite:是一个可变的动态链接点,允许在运行时绑定或重新绑定方法句柄。
5. 优化与性能

Lambda表达式的这种实现方式具有以下几个优点:

  • 内存占用低:不像匿名类那样生成额外的类文件,Lambda表达式通过invokedynamicLambdaMetafactory机制在运行时动态生成。
  • 执行效率高:生成的Lambda表达式实例会被缓存,避免重复生成,提升执行效率。
6.通俗表达

Lambda表达式让Java代码更简洁易读,但它背后的工作原理其实挺复杂的。下面用通俗的语言总结一下它是如何实现的:

  1. 写代码:当你在代码里写了一个Lambda表达式,比如() -> System.out.println("Hello, Lambda!"),它看起来像是一个匿名函数,可以直接使用。

  2. 编译阶段:当你编译代码时,Java编译器不会为每个Lambda表达式生成一个新的类文件(不像以前的匿名类)。相反,编译器会生成一个叫invokedynamic的指令,这个指令在代码运行时才去真正处理Lambda表达式。

  3. 运行时处理

    • 当程序运行到Lambda表达式这行代码时,invokedynamic指令会告诉JVM(Java虚拟机):“嘿,这里有个Lambda表达式,请帮我处理一下。”
    • JVM接到这个请求后,会调用一个特殊的工具(LambdaMetafactory),这个工具负责创建一个能执行Lambda表达式的“小助手”。
  4. 创建“小助手”

    • LambdaMetafactory工具会根据Lambda表达式的内容,动态生成一个对象,这个对象可以像你写的Lambda表达式那样工作。
    • 比如,你的Lambda表达式需要打印“Hello, Lambda!”这句话,这个“小助手”就会被设置好,让它去打印这句话。
  5. 执行和优化

    • 一旦“小助手”被创建,它会被缓存起来,这样以后再遇到相同的Lambda表达式,就不用再创建新的“小助手”,直接使用缓存的那个就行了,这样运行速度更快。

总结

Java通过一些复杂的机制(如invokedynamicLambdaMetafactory)在运行时动态生成和执行Lambda表达式。虽然底层工作很复杂,但它让我们写代码时可以用简单的Lambda表达式来完成任务,而不需要担心性能和额外的类文件生成。这种方式既保持了代码的简洁,又确保了执行的高效。

7.总结

Java通过invokedynamic指令和LambdaMetafactory的动态方法调用机制,实现了Lambda表达式的高效执行。Lambda表达式在编译阶段不会生成新的类文件,而是在运行时动态创建并执行。理解这一机制,有助于Java开发者更好地利用Lambda表达式的强大功能,同时也能深入理解Java虚拟机的动态特性。

总结

Lambda表达式通过简洁的语法和强大的表达能力,使Java代码更加简洁和易读。在底层实现上,Java通过动态方法调用机制来支持Lambda表达式的高效执行。掌握Lambda表达式不仅能够提升代码质量,还能更好地应用Java 8及以上版本的现代特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值