JDK 8新特性之Lambda表达式

本文深入探讨了Java中的Lambda表达式,包括其存在的问题、如何使用Lambda简化匿名内部类、Lambda的标准格式、实现原理、省略格式以及与匿名内部类的区别。通过实例展示了Lambda如何在多线程、函数式接口等场景下提高代码的简洁性和可读性。此外,还介绍了Lambda使用的前提条件,强调了Lambda必须基于函数式接口。
摘要由CSDN通过智能技术生成
目标
了解使用匿名内部类存在的问题
体验 Lambda
使用匿名内部类存在的问题
当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法 , 代码如下:
public class Demo01LambdaIntro {
 public static void main(String[] args) { 
    new Thread(new Runnable() { 
        @Override public void run() { 
        System.out.println("新线程任务执行!"); 
        } 
    }).start(); 
  } 
}
由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交 给一个线程来启动。
代码分析 :
对于 Runnable 的匿名内部类用法,可以分析出几点内容:
  • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心
  • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类
  • 为了省去定义Runnable 实现类的麻烦,不得不使用匿名内部类
  • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在
Lambda 体验
Lambda 是一个 匿名函数 ,可以理解为一段可以传递的代码。
Lambda 表达式写法 , 代码如下:
借助 Java 8 的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的 Lambda 表达式达到相同的效果
public class Demo01LambdaIntro { 
    public static void main(String[] args) { 
        new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程 
    } 
}
这段代码和刚才的执行效果是完全一样的,可以在JDK 8 或更高的编译级别下通过。从代码的语义中可以看出:我们 启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
我们只需要将要执行的代码放到一个Lambda 表达式中,不需要定义类,不需要创建对象。
Lambda 的优点
简化匿名内部类的使用,语法更加简单。
小结
了解了匿名内部类语法冗余,体验了 Lambda 表达式的使用,发现 Lmabda 是简化匿名内部类的简写
Lambda 的标准格式
目标
掌握 Lambda 的标准格式
练习无参数无返回值的 Lambda
练习有参数有返回值的 Lambda
Lambda 的标准格式
Lambda 省去面向对象的条条框框, Lambda 的标准格式格式由 3 个部分 组成:
格式说明:
  • (参数类型 参数名称):参数列表
  • {代码体;}:方法体
  • -> :箭头,分隔参数列表和方法体
Lambda 与方法的对比
匿名内部类
public void run() { 
    System.out.println("aa"); 
}
Lambda
() -> System.out.println("bb!")
练习无参数无返回值的 Lambda
掌握了 Lambda的语法,我们来通过一
interface Swimmable { 
    public abstract void swimming(); 
}
package com.itheima.demo01lambda; 
public class Demo02LambdaUse { 
public static void main(String[] args) {
     goSwimming(new Swimmable() {
        @Override public void swimming() {
         System.out.println("匿名内部类游泳"); 
        } 
    }); 
    goSwimming(() -> { System.out.println("Lambda游泳"); }); 
}
public static void goSwimming(Swimmable swimmable) { 
    swimmable.swimming();
  } 
}
练习有参数有返回值的 Lambda
下面举例演示 java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:
public abstract int compare(T o1, T o2) ;
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
传统写法
如果使用传统的代码对 ArrayList 集合进行排序,写法如下:
public class Person { 
    private String name;
    private int age; 
    private int height; 
    // 省略其他 
}
package com.itheima.demo01lambda; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.Comparator; 
public class Demo03LambdaUse { 
public static void main(String[] args) {
     ArrayList<Person> persons = new ArrayList<>(); 
     persons.add(new Person("刘德华", 58, 174)); 
     persons.add(new Person("张学友", 58, 176));
     persons.add(new Person("刘德华", 54, 171)); 
     persons.add(new Person("黎明", 53, 178));
     Collections.sort(persons, new Comparator<Person>() { 
        @Override public int compare(Person o1, Person o2) { 
            return o1.getAge() - o2.getAge(); 
        } 
    }); 
    for (Person person : persons) { 
        System.out.println(person); 
    } 
  }
}
这种做法在面向对象的思想中,似乎也是“理所当然”的。其中 Comparator 接口的实例(使用了匿名内部类)代表 了“按照年龄从小到大”的排序规则。
Lambda 写法
package com.itheima.demo01lambda;
import java.util.ArrayList;
import java.util.Arrays; 
import java.util.Collections; 
import java.util.Comparator; 
public class Demo03LambdaUse { 
public static void main(String[] args) { 
    ArrayList<Person> persons = new ArrayList<>(); 
    persons.add(new Person("刘德华", 58, 174)); 
    persons.add(new Person("张学友", 58, 176)); 
    persons.add(new Person("刘德华", 54, 171)); 
    persons.add(new Person("黎明", 53, 178)); 
    Collections.sort(persons, (o1, o2) -> { 
        return o1.getAge() - o2.getAge(); }); 
    for (Person person : persons) { 
        System.out.println(person); 
    }
    System.out.println("-----------------"); 
    List<Integer> list = Arrays.asList(11, 22, 33, 44); 
    list.forEach(new Consumer<Integer>() { 
        @Override public void accept(Integer integer) { 
            System.out.println(integer); 
        } 
    });

    System.out.println("-----------------"); 
    list.forEach((s) -> {
         System.out.println(s); 
    }); 
  } 
}
小结
首先学习了 Lambda 表达式的标准格式
( 参数列表 ) -> {
方法体 ;
}
以后我们调用方法时 , 看到参数是接口就可以考虑使用 Lambda 表达式 ,Lambda 表达式相当于是对接口中抽象方法的重 写
了解 Lambda 的实现原理
目标
了解 Lambda 的实现原理
我们现在已经会使用 Lambda 表达式了。现在同学们肯定很好奇 Lambda 是如何实现的,现在我们就来探究 Lambda 表达式的底层实现原理。
@FunctionalInterface 
interface Swimmable { 
    public abstract void swimming(); 
}
public class Demo04LambdaImpl { 
public static void main(String[] args) {
     goSwimming(new Swimmable() { 
        @Override public void swimming() { 
        System.out.println("使用匿名内部类实现游泳"); 
        } 
    }); 
}
public static void goSwimming(Swimmable swimmable) { 
    swimmable.swimming(); 
    } 
}

我们可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class

使用XJad反编译这个类,得到如下代码:

 
package com.itheima.demo01lambda; 
import java.io.PrintStream; 
// Referenced classes of package com.itheima.demo01lambda: 
// Swimmable, Demo04LambdaImpl 
static class Demo04LambdaImpl$1 implements Swimmable { 
    public void swimming() { 
        System.out.println("使用匿名内部类实现游泳"); 
    }
    Demo04LambdaImpl$1() { } 
}
我们再来看看 Lambda 的效果,修改代码如下:
public class Demo04LambdaImpl { 
    public static void main(String[] args) { 
        goSwimming(() -> { System.out.println("Lambda游泳");
     }); 
    }
    public static void goSwimming(Swimmable swimmable) { 
        swimmable.swimming(); 
    } 
}
运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda 并没有在编译的时候产生  一 个新的类。使用XJad 对这个类进行反编译,发现 XJad 报错。使用了 Lambda XJad 反编译工具无法反编译。我们使用 JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。
DOS 命令行输入: javap -c -p 文件名
.class -c:表示对代码进行反汇编
-p:显示所有类和成员
反汇编后效果如下:
C:\Users\>javap -c -p Demo04LambdaImpl.class 
Compiled from "Demo04LambdaImpl.java" 
    public class com.itheima.demo01lambda.Demo04LambdaImpl { 
        public com.itheima.demo01lambda.Demo04LambdaImpl(); 
            Code:
                0: aload_0 
                1: invokespecial #1 // Method java/lang/Object."<init>":()V 
                4: return 
        public static void main(java.lang.String[]); 
            Code:
                0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:                     
    ()Lcom/itheima/demo01lambda/Swimmable; 
                5: invokestatic #3 // Method goSwimming: 
    (Lcom/itheima/demo01lambda/Swimmable;)V 
                8: return 
        public static void goSwimming(com.itheima.demo01lambda.Swimmable); 
            Code:
                0: aload_0 
                1: invokeinterface #4, 1 // InterfaceMethod     
    com/itheima/demo01lambda/Swimmable.swimming:()V 6: return 
        private static void lambda$main$0(); 
            Code:
                0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 
                3: ldc #6 // String Lambda游泳 
                5: invokevirtual #7 // Method java/io/PrintStream.println: 
    (Ljava/lang/String;)V 
                8: return 
}
可以看到在类中多出了一个私有的静态方法 lambda$main$0 。这个方法里面放的是什么内容呢?我们通过断点调试 来看看:

可以确认 lambda$main$0 里面放的就是 Lambda 中的内容,我们可以这么理解 lambda$main$0 方法:
public class Demo04LambdaImpl {
     public static void main(String[] args) { ... }
     private static void lambda$main$0() { 
        System.out.println("Lambda游泳"); 
     }
}
关于这个方法 lambda$main$0 的命名:以 lambda 开头,因为是在 main() 函数里使用了 lambda 表达式,所以带有 $main表示,因为是第一个,所以$0
如何调用这个方法呢?其实 Lambda 在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加 上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类 class 码输出到一个文 件中。使用java 命令如下:
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
根据上面的格式,在命令行输入以下命令:
C:\Users\>java -Djdk.internal.lambda.dumpProxyClasses com.itheima.demo01lambda.Demo04LambdaImpl
Lambda 游泳
执行完毕,可以看到生成一个新的类,效果如下:

 反编译 Demo04LambdaImpl$$Lambda$1.class 这个字节码文件,内容如下:

// Referenced classes of package com.itheima.demo01lambda: 
// Swimmable, Demo04LambdaImpl 
final class Demo04LambdaImpl$$Lambda$1 implements Swimmable {
     public void swimming() { 
       Demo04LambdaImpl.lambda$main$0(); 
    }
    private Demo04LambdaImpl$$Lambda$1() {}
 }
可以看到这个匿名内部类实现了 Swimmable 接口,并且重写了 swimming 方法, swimming 方法调用 Demo04LambdaImpl.lambda$main$0() ,也就是调用 Lambda 中的内容。最后可以将 Lambda 理解为:
public class Demo04LambdaImpl { 
    public static void main(String[] args) {
      goSwimming(new Swimmable() { 
        public void swimming() {
          Demo04LambdaImpl.lambda$main$0(); 
        } 
     }); 
   }
    private static void lambda$main$0() { 
        System.out.println("Lambda表达式游泳"); 
    }
    public static void goSwimming(Swimmable swimmable) {
      swimmable.swimming(); 
    } 
}
小结
匿名内部类在编译的时候会一个class 文件
Lambda 在程序运行的时候形成一个类
1. 在类中新增一个方法, 这个方法的方法体就是 Lambda 表达式中的代码
2. 还会形成一个匿名内部类, 实现接口 , 重写抽象方法
3. 在接口的重写方法中会调用新生成的方法
.
Lambda 省略格式
目标
掌握 Lambda 省略格式
Lambda 标准格式的基础上,使用省略写法的规则为:
1. 小括号内参数的类型可以省略
2. 如果小括号内 有且仅有 个参数 ,则小括号可以省略
3. 如果大括号内 有且仅有 个语句 ,可以同时省略大括号、 return 关键字及语句分号
(int a) -> { 
    return new Person(); 
}
省略后
a -> new Person()
Lambda 的前提条件
目标
掌握 Lambda 的前提条件
Lambda 的语法非常简洁,但是 Lambda 表达式不是随便使用的,使用时有几个条件要特别注意:
1. 方法的参数或局部变量类型必须为接口才能使用 Lambda
2. 接口中有且仅有一个抽象方法
public interface Flyable { 
    public abstract void flying(); 
}
package com.itheima.demo01lambda;
public class Demo05LambdaCondition { 
    public static void main(String[] args) { 
        test01(() -> { }); 
        Flyable s = new Flyable() { 
            @Override public void flying() { } 
        };
        Flyable s2 = () -> { }; 
    }
    public static void test01(Flyable fly) { 
        fly.flying(); 
    } 
}
小结
Lambda 表达式的前提条件 :
1. 方法的参数或变量的类型是接口
2. 这个接口中只能有一个抽象方法
函数式接口
函数式接口在 Java 中是指: 有且仅有 个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而 Java 中的函数式编程体现就是 Lambda ,所以函数式接口就是可以适用于Lambda 使用的接口。只有确保接口中有且仅有一个抽象方法,Java 中的 Lambda 才能顺利地进行推导。
FunctionalInterface 注解
@Override 注解的作用类似, Java 8 中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上:
@FunctionalInterface
public interface Operator {
    void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
Lambda 和匿名内部类对比
目标
了解 Lambda 和匿名内部类在使用上的区别
1. 所需的类型不一样
  • 匿名内部类,需要的类型可以是类,抽象类,接口
  • Lambda表达式,需要的类型必须是接口
2. 抽象方法的数量不一样
  • 匿名内部类所需的接口中抽象方法的数量随意
  • Lambda表达式所需的接口只能有一个抽象方法
3. 实现原理不同
  • 匿名内部类是在编译后会形成class
  • Lambda表达式是在程序运行的时候动态生成class
小结
当接口中只有一个抽象方法时, 建议使用 Lambda 表达式 , 其他其他情况还是需要使用匿名内部类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值