目录
一:使用匿名内部类存在的问题
Lambda表达式写法,代码如下:
二:Lambda的标准格式
三:Lambda的实现原理
四:Lambda省略格式
五:Lambda的前提条件
六:函数式接口
七:Lambda和匿名内部类对比
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("新线程执行代码啦");}}).start();
由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。
匿名内部类做了哪些事情:
1.定义了一个没有名字的类
2.这个类实现了Runnable接口
3.创建了这个类的对象(new)
感觉代码很熟悉,但是我们最关注的是run方法和里面要执行的代码.,但是由于面向对象的特征,我们不得不弄一个类出来,去实现Runnable接口,重写run方法,new这个对象出来,可以体会到使用匿名内部类语法是很冗余的,因此,JDK 8根据这个问题提出一个新特性:Lambda表达式
Lambda表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中(函数就是类中的方法),Lambda就是一个匿名函数,可以理解为一段可以传递的代码, 我们只需要将要执行的代码放到Lambda表达式中即可
public class Demo01LambdaIntro {
public static void main(String[] args) {new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
}
}
这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们 启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。
Lambda表达式的好处: 可以简化匿名内部类,让代码更加精简
小结: 了解了匿名内部类语法冗余,体验了Lambda表达式的使用,发现Lmabda是简化匿名内部类的简写(参数类型 参数名称) -> {代码体;
}
格式说明:
public void run() {System.out.println("aa");
}
Lambda
() -> System.out.println("bb!")
掌握了Lambda的语法,我们来通过一个案例熟悉Lambda的使用。 定义一个接口,并定义一个抽象方法
- 练习无参数无返回值的Lambda
interface Swimmable {public abstract void swimming();
}
public class Demo02LambdaUse {public static void main(String[] args) {//传统方式goSwimming(new Swimmable() {@Overridepublic void swimming() {System.out.println("凤姐 自由泳.");}});//Lambda表达式goSwimming(() -> {System.out.println("如花 蛙泳");});}// 小结:以后我们看到方法的参数是接口就可以考虑使用Lambda表达式// Lambda表达式相当于是对接口抽象方法的重写// 练习无参数无返回值的Lambdapublic static void goSwimming(Swimmable s) {s.swimming();}}
- 练习有参数有返回值的Lambda
定义一个接口,并定义一个抽象方法,带有参数
interface Smokeable {public abstract int smoking(String name);
}
goSmoking(new Smokeable() {@Overridepublic void smoking() {System.out.println("有一根"+name+"的烟");return 5;}});goSmoking((String name) ->{System.out.println("Lambda 有"+name+"的烟");return 6;});// 练习有参数有返回值的Lambdapublic static void goSmoking(Smokeable s) {int i= s.smoking("China made");System.out.println(i);}
下面举例演示 java.util.Comparatorpublic class Person {
private String name;
private int age;
private int height;
// 省略其他
}
// 练习有参数有返回值的Lambda
public class Demo03LambdaParam {public static void main(String[] args) {ArrayList 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() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge() - o2.getAge(); // 升序排序}});*/Collections.sort(persons, (Person o1, Person o2) -> {return o2.getAge() - o1.getAge(); // 降序});for (Person person : persons) {System.out.println(person);}System.out.println("-----------");persons.forEach((t) -> {System.out.println(t);});}
}
小结:
首先学习了Lambda表达式的标准格式
(参数列表) -> {方法体;
}
以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,Lambda表达式相当于是对接口中抽象方法的重写
@FunctionalInterface
interface Swimmable {
public abstract void swimming();
}
public class Demo04LambdaImpl {
public static void main(String[] args) {goSwimming(new Swimmable() {@Overridepublic void swimming() {System.out.println("使用匿名内部类实现游泳");
}
});
}public static void goSwimming(Swimmable swimmable) {swimmable.swimming();
}
}
我们可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class
使用XJad反编译这个类,得到如下代码:
package com.tangyuan.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.tangyuan.demo01lambda.Demo04LambdaImpl();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:
()Lcom/tangyuan/demo01lambda/Swimmable;
5: invokestatic #3 // Method goSwimming:
(Lcom/tangyuan/demo01lambda/Swimmable;)V
8: return
public static void goSwimming(com.itheima.demo01lambda.Swimmable);
Code:
0: aload_0
1: invokeinterface #4, 1 // InterfaceMethod
com/tangyuan/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.tangyuan.demo01lambda.Demo04LambdaImpl
Lambda游泳
执行完毕,可以看到生成一个新的类,效果如下:
反编译 Demo04LambdaImpl$$Lambda$1.class 这个字节码文件,内容如下:
// Referenced classes of package com.tnagyuan.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. 在接口的重写方法中会调用新生成的方法
(int a) -> {
return new Person();
}
省略后
a -> new Person()
案例如下:
public static void main(String[] args) {ArrayList 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);}---------------------------------省略后---------------------------Collections.sort(persons, (o1, o2) -> o2.getAge() - o1.getAge());persons.forEach(t -> System.out.println(t));}
public interface Flyable {public abstract void flying();
}
public class Demo06LambdaCondition {public static void main(String[] args) {// 方法的参数或局部变量类型必须为接口才能使用Lambdatest(() -> {});Runnable r = new Runnable() {@Overridepublic void run() {System.out.println("aa");}};Flyable f = () -> {System.out.println("我会飞啦");};}public static void test(Flyable a) {new Person() {};}}// 只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda
@FunctionalInterface // 检测这个接口是不是只有一个抽象方法
interface Flyable {// 接口中有且仅有一个抽象方法public abstract void eat();// public abstract void eat2();
}
小结:
Lambda表达式的前提条件:
1. 方法的参数或变量的类型是接口
2. 这个接口中只能有一个抽象方法
@FunctionalInterface
public interface Operator {void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样 下一篇:自定义类型:结构体,枚举,联合