JDK 8新特性之Lambda表达式

目录

一:使用匿名内部类存在的问题

Lambda表达式写法,代码如下:

二:Lambda的标准格式

三:Lambda的实现原理

四:Lambda省略格式

五:Lambda的前提条件

六:函数式接口

七:Lambda和匿名内部类对比


一:使用匿名内部类存在的问题

            当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法 , 代码如下:
   new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程执行代码啦");
            }
        }).start();

       由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。

    匿名内部类做了哪些事情:

         1.定义了一个没有名字的类
         2.这个类实现了Runnable接口
         3.创建了这个类的对象(new)

对于 Runnable 的匿名内部类用法,可以分析出几点内容:
  • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心
  • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类
  • 为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部类
  • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在

           感觉代码很熟悉,但是我们最关注的是run方法和里面要执行的代码.,但是由于面向对象的特征,我们不得不弄一个类出来,去实现Runnable接口,重写run方法,new这个对象出来,可以体会到使用匿名内部类语法是很冗余的,因此,JDK 8根据这个问题提出一个新特性:Lambda表达式

              Lambda表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中(函数就是类中的方法),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 的标准格式格式由 3 个部分 组成:
(参数类型 参数名称) -> {
    代码体;
}
  格式说明:
  • (参数类型 参数名称):参数列表
  • {代码体;}:方法体
  • -> :箭头,分隔参数列表和方法体,起到连接作用
   Lambda 与方法的对比
            匿名内部类
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() {
            @Override
            public void swimming() {
                System.out.println("凤姐 自由泳.");
            }
        });

         
       //Lambda表达式
        goSwimming(() -> {
            System.out.println("如花 蛙泳");
        });
    }


      // 小结:以后我们看到方法的参数是接口就可以考虑使用Lambda表达式
     //  Lambda表达式相当于是对接口抽象方法的重写

    // 练习无参数无返回值的Lambda
    public static void goSwimming(Swimmable s) {
        s.swimming();
    }

   
}

 

  • 练习有参数有返回值的Lambda

定义一个接口,并定义一个抽象方法,带有参数

interface Smokeable {
     public abstract int smoking(String name);
}
 
 goSmoking(new Smokeable() {
            @Override
            public void smoking() {
                System.out.println("有一根"+name+"的烟");
                 return 5;
            }
        });


 goSmoking((String name) ->{
       System.out.println("Lambda 有"+name+"的烟");
         return 6;
   });


 // 练习有参数有返回值的Lambda
    public static void goSmoking(Smokeable s) {
      int i= s.smoking("China made");
     System.out.println(i);
    }
下面举例演示 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;
// 省略其他
}
// 练习有参数有返回值的Lambda
public class Demo03LambdaParam {
    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(); // 升序排序
            }
        });*/

        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 表达式相当于是对接口中抽象方法的重写

三: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.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."<init>":()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. 在接口的重写方法中会调用新生成的方法

四:Lambda省略格式

Lambda 标准格式的基础上,使用省略写法的规则为:
1. 小括号内参数的类型可以省略
2. 如果小括号内 有且仅有一个参数 ,则小括号可以省略
3. 如果大括号内 有且仅有一个语句 ,可以同时省略大括号、 return 关键字及语句分号
(int a) -> {
return new Person();
}
省略后
a -> new Person()

案例如下:

   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);
           }


  ---------------------------------省略后---------------------------

     Collections.sort(persons, (o1, o2) -> o2.getAge() - o1.getAge());

        persons.forEach(t -> System.out.println(t));
    }

五:Lambda的前提条件

Lambda 的语法非常简洁,但是 Lambda 表达式不是随便使用的,使用时有几个条件要特别注意:
1. 方法的参数或局部变量类型必须为接口才能使用 Lambda
2. 接口中有且仅有一个抽象方法
public interface Flyable {
   public abstract void flying();
}
public class Demo06LambdaCondition {
    public static void main(String[] args) {
        // 方法的参数或局部变量类型必须为接口才能使用Lambda
        test(() -> {
        });

        Runnable r = new Runnable() {
            @Override
            public 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. 这个接口中只能有一个抽象方法

六:函数式接口

              函数式接口在 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 表达式 , 其他其他情况还是需要使用匿名内部类
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值