JAVA8新特性---Lambda表达式

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。

Lambda表达式介绍

当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。这个时候可以只用匿名类

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行代码");
            }
        }).start();

在这个代码中匿名内部类所做的事情有:

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

可以看出使用匿名内部类语法是很冗余的,其实我们最关注的是run方法和里面要执行的代码 

使用Lambda表达式写法,我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。

        //使用Lambda表达式
        new Thread(() -> {
            System.out.println("Lambda表达式执行");
        }).start();

Lambda表达式的优点

可以简化匿名内部类,是代码变得更加简洁

Lambda的标准格式

Lambda表达式是一个匿名函数,函数相当与Java中的方法
(参数列表) -> {
    方法体
}
->没有实际含义起到连接的作用
public class LambdaDemo03 {
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("张三",32,175));
        persons.add(new Person("李四",28,165));
        persons.add(new Person("王五",46,205));
        persons.add(new Person("赵六",16,185));
        /*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 o1.getAge()-o2.getAge();
        });
        persons.forEach((t)->{
            System.out.println(t);
        });
    }
}

无参数无返回值的Lambda和有参数有返回值的Lambda

public interface Smokeable {
    public abstract int smoking(String name);
}
public interface Swimmable {
    public abstract void swimming();
}
public class LambdaDemo02 {
    public static void main(String[] args) {
        goSwimming(new Swimmable() {
            @Override
            public void swimming() {
                System.out.println("我是匿名内部类的游泳");
            }
        });
        //无参数无返回值的Lambda
        //可以认为Lambda表达式就是对接口中的抽象方法的重写
        goSwimming(()->{
            System.out.println("我是Lambda的游泳");
        });
        System.out.println("----------------");
        goSmoking(new Smokeable() {
            @Override
            public int smoking(String name) {
                System.out.print("匿名内部类抽了"+name+"的烟");
                return 1;
            }
        });

        //有参数有返回值的Lambda
        goSmoking((String name)->{
            System.out.print("Lambda抽了"+name+"的烟");
            return 2;
        });
    }

    public static void goSwimming(Swimmable s){
        s.swimming();
    }

    public static void goSmoking(Smokeable s){
        int result=s.smoking("普皖");
        System.out.println(result+"根");
    }
}

Lambda的实现原理

首先是匿名内部类

public class LambdaDemo04 {
    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();
    }
}

将上面的代码执行之后可以在存储的位置找到有$的.class文件

将它反编译(反编译可以参考http://t.csdn.cn/X0KQK)之后可以得到这样的内容

而Lambda在编译之后并没有生成和匿名内部类生成的一样的文件,同时使用反编译工具还会出现报错的情况,我们就可以使用JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。

可以在DOS命令行输入

javap -c -p 文件名.class

其中-c:表示对代码进行反汇编   -p:显示所有类和成员

就得到了这样的效果

 在这里我们可以看到在类中多出了一个私有的静态方法 lambda$main$0 。

这时我们可以在Lambda中设置断点

这也就可以确认 lambda$main$0 里面放的就是Lambda中的内容,可以将Lambda表达式中的内容理解为:

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 要运行的包名.类名 

注意:这里包含了包名,所以在使用之前要退出到包外面哦

这样的话就生成了一个.class文件,将其反编译之后得到了如下内容

可以看到这个匿名内部类实现了 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();
    }
}

总结:

Lambda在程序运行的时候形成一个类

  1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
  2.  还会形成一个匿名内部类,实现接口,重写抽象方法
  3.  在接口的重写方法中会调用新生成的方法. 

Lambda省略格式

在Lambda标准格式的基础上,使用省略写法的规则为:

1. 小括号内参数的类型可以省略

2. 如果小括号内有且仅有一个参数,则小括号可以省略

3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号

这样的话前面所写过的LambdaDemo03中的代码可以省略成这样:

不过省略的前提都是建立在规则之上的 

Lambda的前提条件

Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,它是由前提的

1.方法的参数或局部变量类型必须为接口才能使用Lambda

2. 接口中有且仅有一个抽象方法

public class LambdaDemo05 {
    public static void main(String[] args) {
        //方法的参数或局部变量类型必须为接口才能使用Lambda

        test(()->{

        });

        Flyable f = () ->{
            System.out.println("我会飞");
        };
    }

    public static void test(Flyable a){

    }
}
// 只有一个抽象方法的接口成为函数式接口,我们就能使用Lambda
interface Flyable{
    //接口中有且只能有一个方法
    public abstract void eat();
//    public abstract void eat();
}

函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以 适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

FunctionalInterface注解

与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即 使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

Lambda和匿名内部类对比

1. 所需的类型不一样

  • 匿名内部类,需要的类型可以是类,抽象类,接口
  • Lambda表达式,需要的类型必须是接口

2. 抽象方法的数量不一样

  • 匿名内部类所需的接口中抽象方法的数量随意
  • Lambda表达式所需的接口只能有一个抽象方法

3. 实现原理不同

  • 匿名内部类是在编译后会形成class
  • Lambda表达式是在程序运行的时候动态生成class

当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类

Lambda表达式的冗余场景

这样的一个表达式:

LambdaInterface5 lambdaInterface5=(a->{
           return a+10; 
});

首先通过省略我们可以得到下面的式子:

LambdaInterface5 lambdaInterface7 = a -> a+10

同时还可以写一个方法,让Lambda表达式更加简洁

    public  static  int add(int a){
        return a+10;
    }
//Lambda表达式就可以变成这样
LambdaInterface5 lambdaInterface8 = a -> add(a);

在这个Lambda表达式中,我们在Lambda中所指定的方法,已经有其他方法存在相同的方案,而这个Lambda起到的作用也仅仅知识调用了这个方法而已。可以直接将其引用过去。就是使用“方法引用”的方式,代码如下

LambdaInterface5 lambdaInterface8 = LambdaDemo02::add;

方法引用

方法引用的格式

符号表示为     ::

符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用

应用场景 : 如果 Lambda 所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。

常见的引用方式

1. instanceName::methodName 对象 ::方法名    如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法
2. ClassName::staticMethodName 类名 ::静态方法      由于在 java.lang.System 类中已经存在了静态方法 currentTimeMillis ,所以当我们需要通过 Lambda 来调用该 方法时 , 可以使用方法引用
3. ClassName::methodName 类名 :: 普通方法
4. ClassName::new 类名 ::new 调用的构造器
5. TypeName[]::new String[]::new 调用数组的构造器

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

放在糖果旁的是我很想回忆的甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值