Java8新特性:Lambda表达式

2023-07-12 15:16:38 浏览数 (1)

1、初体验

目标:了解使用匿名内部类存在的问题,体验Lambda 匿名内部类存在的问题:当需要启动一个线程去完成任务时,通常会通过Runnable 接口来定义任务内容,并使用Thread 类来启动该线程。

1、传统写法
代码语言:javascript复制
public class LambdaIntro01 {
    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 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在。
2、lambda写法

Lambda是一个匿名函数,可以理解为一段可以传递的代码。

代码语言:javascript复制
public class LambdaIntro01 {
    public static void main(String[] args) {
        new Thread(() ->{
            System.out.println("启动一个线程");
        }).start();
    }
}

代码分析

这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。

2、Lambda的标准格式

1、格式

Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:

代码语言:javascript复制
(参数类型 参数名称) -> {
    代码体;
}

格式说明

(参数类型 参数名称):参数列表 {代码体;}:方法体 -> :箭头,分隔参数列表和方法体

2、无参数无返回值的Lambda
代码语言:javascript复制
public interface PhoneStore {
    public abstract void buy();
}
代码语言:javascript复制
public class LambdaUse02 {

    public static void main(String[] args) {
        goStore(new PhoneStore() {
            @Override
            public void buy() {
                System.out.println("买华为手机");
            }
        });

        goStore(() -> System.out.println("买小米手机"));
    }

    public static void goStore(PhoneStore phoneStore){
        phoneStore.buy();
    }
}
3、有参数有返回值的Lambda

下面举例演示java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:

代码语言:javascript复制
public abstract int compare(T o1, T o2);

当需要对一个对象集合进行排序时, Collections.sort 方法需要一个Comparator 接口实例来指定排序的规则。

实体类

代码语言:javascript复制
public class Person {

    private String name;

    private Integer age;

    private Date birthday;

    public Person(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", Person.class.getSimpleName()   "[", "]")
                .add("name='"   name   "'")
                .add("age="   age)
                .add("birthday="   birthday)
                .toString();
    }
}

传统写法VS Lambda写法

传统写法

代码语言:javascript复制
public class Lambda03 {
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华", 58, new Date()));
        persons.add(new Person("张学友", 58, new Date()));
        persons.add(new Person("刘德华", 54, new Date()));
        persons.add(new Person("黎明", 53, new Date()));

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

Lambda写法

代码语言:javascript复制
public class Lambda03 {
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华", 58, new Date()));
        persons.add(new Person("张学友", 58, new Date()));
        persons.add(new Person("刘德华", 54, new Date()));
        persons.add(new Person("黎明", 53, new Date()));
        
        Collections.sort(persons,(o1, o2)->{
            return o1.getAge() - o2.getAge();
        });
        //另一种写法
        Collections.sort(persons, Comparator.comparingInt(Person::getAge));

        for (Person person : persons) {
            System.out.println(person);
        }
    }
}

3、省略格式

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

  1. 小括号内参数的类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
代码语言:javascript复制
(int a) ->{
    return new Person();
}

省略后

代码语言:javascript复制
a -> new Person();

4、前提条件

Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:

  1. 方法的参数或局部变量类型必须为接口才能使用Lambda
  2. 接口中有且仅有一个抽象方法
代码语言:javascript复制
public interface Flyable {

    public abstract void flying();
}
代码语言:javascript复制
public class Lambda04 {

    public static void main(String[] args) {
        test(() ->{});

        Flyable flyable = new Flyable() {
            @Override
            public void flying() {

            }
        };

        Flyable flyable1 = () ->{

        };
    }

    public static void test(Flyable flyable){
        flyable.flying();
    }
}

5、函数式接口

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

FunctionalInterface注解

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

代码语言:javascript复制
@FunctionalInterface
public interface Operator {
    void myMethod();
}

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

6、Lambda和匿名内部类对比

不同

Lambda

匿名内部类

所需的类型

需要的类型必须是接口

需要的类型可以是类,抽象类,接口

抽象方法的数量

所需的接口只能有一个抽象方法

所需的接口中抽象方法的数量随意

实现原理

是在程序运行的时候动态生成class

是在编译后会形成class

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

0 人点赞