Java8新特性之Lambda表达式

1. Lambda表达式引入

当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable 接口来定义任务内容,并使用 java.lang.Thread 类来启动该线程。

传统写法,代码如下:

public class ThreadDemo {
	public static void main(String[] args) {
		new Thread(new Runnable() {
            //run方法用于指定线程的具体任务内容
			@Override
			public void run() {
				System.out.println("多线程任务执行!");
			}
		}).start();
	}
}

本着”一切皆对象”的思想,这种做法是无可厚非的:首先创建一个 Runnable 接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。

代码分析:

对于 Runnable 的匿名内部类用法,可以分析出几点内容:

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

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

Lambda 表达式是在Java 8 语言中引入的新特性,借助 Java 8 新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的 Lambda表达式 达到等效:

public class LambdaRunnableDemo {
	public static void main(String[] args) {
		new Thread(() -> System.out.println("多线程任务执行!")).start();
	}
}

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

2. Lambda表达式的格式

标准格式:

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

  • 一些参数
  • 一个箭头
  • 一段代码

Lambda表达式的标准格式为:

(形参列表) -> {Lambda体}

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
  • -> 是新引入的操作符,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。

匿名内部类与lambda表达式对比

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("多线程任务执行!");
    }
}).start();

仔细分析该代码中,Runnable接口只有一个 run 方法的定义:

  • public abstract void run();

即制定了一种做事情的方案(其实就是一个方法):

  • 无参数:不需要任何条件即可执行该方案。
  • 无返回值:该方案不产生任何结果。
  • 代码块(方法体):该方案的具体执行步骤。

同样的语义体现在Lambda语法中更加简单:

() -> System.out.println("多线程任务执行!")
  • 前面的一对小括号即run方法的参数(无),代表不需要任何条件;
  • 中间的一个箭头代表将前面的参数传递给后面的代码;
  • 后面的输出语句即业务逻辑代码。

下面举例演示java.util.Comparator<T>接口的使用场景,接口的抽象方法为:public abstract int compare(T o1, T o2);

当需要对一个对象数组进行排序时,Arrays.sort 方法需要一个 Comparator 接口的实现类对象来指定排序的规则。假设有一个Student类,含有String nameint age两个成员变量:

public class Student { 
    private String name;
    private int age;
    
    // 省略构造器、toString方法与Getter Setter 
}

传统写法

如果使用传统的代码对Person[]数组进行排序,写法如下:

public class ComparatorDemo {
    public static void main(String[] args) {
        // 本来年龄乱序的对象数组
        Student[] array = { new Student("lily", 19),
                new Student("jack", 18),
                new Student("mark", 20) };

        // 匿名内部类
        Comparator<Student> com = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 指定排序规则,按年龄升序排序
                return o1.getAge() - o2.getAge();
            }
        };

        Arrays.sort(array, com); // 第二个参数为排序规则,即Comparator接口的实现类对象

        for (Student student : array) {
            System.out.println(student);
        }
    }
}

这种做法在面向对象的思想中,似乎也是“理所当然”的。其中Comparator接口的实现类对象(使用了匿名内部类)代表了“按照年龄从小到大”的排序规则。

代码分析

下面我们来搞清楚上述代码真正要做什么事情。

  • 为了排序,Arrays.sort方法需要排序规则,即Comparator接口的实现类对象,抽象方法compare是关键;
  • 为了指定compare的方法体,不得不需要Comparator接口的实现类;
  • 为了省去定义一个Comparator接口实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象compare方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 实际上,只有参数和方法体才是关键

Lambda写法

public class ComparatorLambdaDemo {
    public static void main(String[] args) {
        Student[] array = { new Student("lily", 19),
                new Student("jack", 18),
                new Student("mark", 20) };

        // 匿名内部类 简化为 lambda表达式:隐含地new了一个Comparator接口的实现类对象,并实现了compare方法
        Comparator<Student> com = (Student o1, Student o2) -> {
            // 指定排序规则,按升序排序
            return o1.getAge() - o2.getAge();
        };

        Arrays.sort(array, com);

        for (Student student : array) {
            System.out.println(student);
        }
    }
}

省略格式:

省略规则

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

  1. (形参列表) 内参数的类型可以省略;

  2. 如果(形参列表)有且仅有一个参数,则 () 可以省略,仅保留参数名;

  3. 如果{lambda体}有且仅有一条语句,则无论是否有返回值,都可以省略大括号{}、return关键字及语句分号。

    //返回值为5
    () -> 5
    

可推导即可省略

Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以推导得知的信息,都可以省略。例如上例还可以使用Lambda的省略写法:

Arrays.sort(array, (o1, o2) -> o1.getAge() - o2.getAge());

3. Lambda表达式的前提条件

Lambda的语法非常简洁,去除了面向对象复杂的束缚。但有使用的前提条件:

  1. 必须有接口,且要求接口中有且仅有一个抽象方法
    无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中有且仅有一个抽象方法时,才可以使用Lambda表达式。

  2. 必须有接口类型的变量或接口作为方法形参。

    Lambda表达式是用来给接口类型的变量或方法形参赋值的。也就是变量类型或方法形参必须为Lambda表达式对应的接口类型,才能使用Lambda表达式作为该接口的实现类对象。

    代码示例:

    public interface MyInterface {
        void run(String str);
    }
    
    public class LambdaTest {
    
        public static void main(String[] args) {
            //lambda表达式替代匿名内部类,并赋值为接口类型的变量
            MyInterface myInterface = str -> System.out.println(str + ", run方法执行了");
    		//执行run方法
            myInterface.run("hello");
        }
    }
    

4. Lambda表达式的本质

有且仅有一个抽象方法的接口,称为 函数式接口(该接口可以包含非抽象方法)。

表面上看Lambda表达式是一个匿名函数,其本质是:函数式接口实现类的对象,用于实现 函数式接口抽象方法

5. Lambda表达式和匿名内部类的区别

  • 所需的类型不同

匿名内部类,需要的类型可以是普通类、抽象类、接口

Lambda表达式,需要的类型必须是接口

  • 抽象方法的数量同

匿名内部类所需的接口中抽象方法的数量不限

Lambda表达式所需的接口只能有一个抽象方法

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值