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));
- paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外 当只有一个推断类型时可以省略掉圆括号。
- ->:可理解为“被用于”的意思
- 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不返回,这里的代码块块等同于方法的方法体。表达式也是一样,可以返回值或者不返回。
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类型的对象:
因此,编译器知道我们传入的是一个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 表达式实现了 Runnable
的 run
方法?
实际上,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 表达式中,
expression
或
statements
是否有返回值,取决于你所实现的接口方法的返回类型。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(...)
:forEach
是Iterable
接口中的一个默认方法,它接受一个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 会根据目标类型和上下文自动匹配接口方法。
参考
……