目录

java-8新特性Lambda表达式,以及其和匿名内部类的比较

java 8新特性——Lambda表达式,以及其和匿名内部类的比较

1.Lambda表达式

Lambda 表达式是 Java 8 最具革命性的新特性之一,它允许你用 更简洁的方式表示函数式接口 (Functional Interface)。通过 Lambda 表达式,我们可以避免冗长的匿名内部类代码 ,使代码更加简洁。

Lambda表达式基本语法:

(parameters) -> expression
or
(parameters) -> { statements; }
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
//name -> System.out.println(name) 是一个 Lambda 表达式,用来替代传统的匿名内部类。
names.forEach(name -> System.out.println(name));
  1. paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外 当只有一个推断类型时可以省略掉圆括号。
  2. ->:可理解为“被用于”的意思
  3. 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不返回,这里的代码块块等同于方法的方法体。表达式也是一样,可以返回值或者不返回。

2. 匿名内部类

匿名内部类是一种没有名称的类,通常用于实现接口或继承类。它可以在代码中定义并实例化,同时可以访问外部类的成员变量。

匿名类基本语法如下:

new InterfaceName() {
    // 实现接口方法
    @Override
    public void method() {
        // 实现内容
    }
};

3. 两者之间的关系

通过线程代码举例:使用Runnable接口实现

public class MyClass {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread is running...");
            }
        });
        thread.start();
    }
}

在该例子中,Runnable的实现就是通过匿名内部类的方式。

使用Lambda表达式:

public class MyClass {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("Thread is running..."));
        thread.start();
    }
}

我们能够看到这个代码的实现十分简洁,但是有一个疑问—— 为什么这个Lambda表达式甚至连需要的类是哪一个,要实现的方法是哪一个都没有声明,就直接能够实现和匿名内部类等效的效果

答案就在Lambda 表达式的推断和省略。

3.1接口的类型推断

在调用Tread构造函数的时候,他接受的是一个Runnable类型的对象:

https://i-blog.csdnimg.cn/direct/2d15c45f27c54f08bf43e01b42f5abac.png

因此,编译器知道我们传入的是一个Runnable类型的实例,并自动推断出 Lambda 表达式实现了Runnable接口的run方法。

3.2 run方法的推断

Runnable接口只有一个抽象方法run(),Lambda 表达式 () -> System.out.println("Thread is running...") 自动映射为对 run 方法的实现。换句话说,Lambda 表达式的 () 表示 run 方法的参数(没有参数),而 System.out.println("Thread is running...") 则是 run() 方法的实现体。

省略的内容

  • 方法签名 :Lambda 表达式省略了方法签名。在传统的匿名内部类中,你需要显式地实现接口的 run 方法,而在 Lambda 表达式中 ,编译器会根据接口类型推断出方法签名

例如,匿名内部类的形式是:

new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread is running...");
    }
}

在这里, Runnable 接口的 run 方法需要明确被覆写,而 Lambda 表达式则通过上下文推断来完成这一步。

  • 类声明 :Lambda 表达式省略了内部类的声明部分。匿名内部类需要显式地使用 new Runnable() 来创建一个对象,并通过 @Override 明确地实现 run 方法。而 Lambda 通过简洁的语法直接表示了这个函数式接口的实现。

如何知道 Lambda 表达式实现了 Runnablerun 方法?

实际上,Lambda 表达式并不需要明确声明自己实现了哪个接口。编译器根据上下文的类型推断,知道传递给 Thread 构造器的 Lambda 表达式应该对应 Runnable 接口。编译器会将 Lambda 表达式转换成一个 Runnable 类型的对象,它自动调用 Runnable 接口的 run 方法。

  • 如果你将 Lambda 表达式赋值给一个变量,编译器也会通过目标类型推断来确定应该实现哪个方法。

    例如:

Runnable task = () -> System.out.println("Thread is running...");
task.run();  // 调用 run 方法

这里,Lambda 表达式会被视为 Runnable 接口的一个实现,因此它的 run 方法会被调用。

4. Lambda表达式的返回值

在 Lambda 表达式中, expressionstatements 是否有返回值,取决于你所实现的接口方法的返回类型。Lambda 表达式的返回类型是由接口的方法签名(即该方法的返回类型)决定的。

4.1. 无返回值的 Lambda 表达式(函数式接口的 void 方法)

//names是一个String类型的List
names.forEach(name -> System.out.println(name));

//等价于
names.forEach(new Consumer<String>() {
    @Override
    public void accept(String name) {
        System.out.println(name);
    }
});
  • names.forEach(...)forEachIterable 接口中的一个默认方法,它接受一个 Consumer 类型的参数。 Consumer 是一个函数式接口,它有一个抽象方法 accept(T t) ,用于对传入的参数执行某个操作。
  • name -> System.out.println(name) :这是 Lambda 表达式,它表示了 Consumer.accept 方法的实现。对于每个元素 name ,执行 System.out.println(name)

这里 ,Consumer.accept() 方法的返回类型是 void ,所以 Lambda 表达式中的 System.out.println(name) 语句没有返回值,直接执行操作。

4.2. 有返回值的 Lambda 表达式(函数式接口的返回类型不是 void

如果接口的方法返回一个具体的值,那么 Lambda 表达式中必须使用 return 语句来返回一个值,且返回类型必须和接口方法的返回类型匹配。

示例: 假设我们有一个函数式接口 Function ,它的 apply 方法有返回值:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

我们可以通过 Lambda 表达式实现 apply 方法并返回一个值:

Function<Integer, String> convert = num -> "Number is " + num;
System.out.println(convert.apply(5));  // 输出 "Number is 5"

在这个例子中, Function.apply() 方法的返回类型是 String ,因此 Lambda 表达式中 num -> "Number is " + num 必须返回一个 String 类型的值。

等价的匿名内部类

Function<Integer, String> convert = new Function<Integer, String>() {
    @Override
    public String apply(Integer num) {
        return "Number is " + num;
    }
};

总而言之:

  • 没有返回值的 Lambda 表达式 :适用于那些方法返回类型是 void 的接口,例如 Consumer 接口。
  • 有返回值的 Lambda 表达式 :适用于那些方法返回类型不是 void 的接口,例如 Function 接口。在这种情况下,Lambda 表达式需要返回一个值,且返回类型必须与接口方法的签名一致。
  • 单一表达式 :如果 Lambda 表达式仅包含一个表达式(没有大括号{}),则该表达式的结果会自动作为返回值。
  • 多语句的 Lambda 表达式 :如果 Lambda 表达式包含多行语句(使用到{}),需要使用 return 语句显式返回结果。

expression(表达式)与statement(语句)的区别:

前者是能够返回一个值(或者说是计算出一个结果),后者是执行某种操作或控制流程的代码。有时表达式可以作为语句的一部分,构成更大的代码块。

int result = 5 + 3; // 这是一个语句但包含了表达式 5 + 3

5.总结

Lambda 表达式的核心是 上下文推断 ,它根据你使用 Lambda 的场景(比如传递给 Thread 的构造函数)自动推断出你希望实现哪个接口的方法。虽然 Lambda 表达式看起来很简洁,但它实际上遵循了接口类型的约定,自动识别并实现了接口的抽象方法。你不需要显式地写出方法签名,Lambda 会根据目标类型和上下文自动匹配接口方法。

参考

……