Lambda表达式详解

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

当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法, 代码如下:
public class LambdaTest1 {
    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 LambdaTest2 {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
    }
}
这段代码和刚才的执行效果是完全一样的,可以在 JDK 8 或更高的编译级别下通过。从代码的语义中可以看出:我们 启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
我们只需要将要执行的代码放到一个 Lambda 表达式中,不需要定义类,不需要创建对象

Lambda的优点

一句话总结:简化匿名内部类的使用,语法更加简单

Lambda的标准格式

Lambda 省去面向对象的条条框框, Lambda 的标准格式格式由 3 个部分 组成:
( 参数类型 参数名称 ) -> {
        代码体;
}
格式说明:
  • (参数类型 参数名称):参数列表
  • {代码体;}:方法体
  • -> :箭头,分隔参数列表和方法体
Lambda与方法的对比:
匿名内部类:
public void run() {
    System.out.println("aa");
}

Lambda:

() -> System.out.println("bb!")
无参数无返回值的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("匿名内部类游泳");
        }
    });
        //调用Lambda表达式来写
        goSwimming(() -> {
            System.out.println("Lambda游泳");
        });
    }

    //定义一个方法,参数传一个Swimmable接口
    public static void goSwimming(Swimmable s) {
        s.swimming();
    }
}

小结:以后我们看到方法的参数是接口就可以考虑使用Lambda表达式

我们可以这么认为:Lambda表达式就是对接口中的抽象方法的重写,但是接口必须是函数式接口,有且仅有一个抽象方法的接口。

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

省略后:

a -> new Person()

Lambda使用的前提条件

Lambda 的语法非常简洁,但是 Lambda 表达式不是随便使用的,使用时有几个条件要特别注意:
1. 方法的参数或局部变量类型必须为接口才能使用 Lambda
2. 接口中有且仅有一个抽象方法

函数式接口

函数式接口在 Java 中是指: 有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而 Java 中的函数式编程体现就是 Lambda ,所以函数式接口就是可以
适用于 Lambda 使用的接口。只有确保接口中有且仅有一个抽象方法, Java 中的 Lambda 才能顺利地进行推导。
FunctionalInterface 注解
@Override 注解的作用类似, Java 8 中专门为函数式接口引入了一个新的注 @FunctionalInterface 。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface Operator {
    void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

Lambda和匿名内部类对比

1. 所需的类型不一样

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

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

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

3. 实现原理不同

  • 匿名内部类是在编译后会形成class
  • Lambda表达式是在程序运行的时候动态生成class
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类
  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值