前言
- 之前在学习匿名内部类,lambda 和方法引用时,对其的理解停留在表面,没有思考它们之间的联系。一周后在回看代码有了更深层次的理解,现在从“行为传递”的角度总结知识点
- 初接触 Java,部分表达不当望谅解
1. 匿名内部类是什么
顾名思义,匿名内部类即为没有显示定义取名的类,常用于:
- 继承父类并且立即创建一个子类对象
- 实现接口并且立即创建一个实现类对象
下面我以更常见的第二种情况来讲一下匿名内部类的定义方法
public interface Sleep {
void sleep();
}
Sleep s1 = new Sleep() {
@Override
public void sleep() {
System.out.println("Minji sleeps tight");
}
};
接口是不能直接实例化的,这时候就需要引入匿名内部类。
👉 更准确地说,这里做了两件事:
- 临时定义一个接口的实现类
- 创建这个实现类的对象
匿名内部类本质上是一个没有显式命名的类,同时完成了“定义 + 创建”。
2. 匿名内部类与回调
匿名内部类之所以常见,不是因为开发者喜欢定义“临时类”,而是因为许多 API 设计本身就需要调用者传一个行为对象。
public class Test {
public static void main(String[] args) {
// 注意 startsleep 在执行中的回调
Sleep s1 = new Sleep() {
@Override
public void sleep() {
System.out.println("Minji sleeps tight");
}
};
startSleep(s1);
// 可以直接作为参数传入方法
startSleep(new Sleep() {
@Override
public void sleep() {
System.out.println("Hanni sleeps tight");
}
});
}
public static void startSleep(Sleep sleep){
System.out.println("夜深了");
sleep.sleep();
}
}
在执行到 sleep.sleep() 时,会调用匿名内部类中定义的具体方法。
这就是典型的回调思想:
方法的执行流程先写好,但是某一步具体怎么执行,由调用方传入
👉 匿名内部类的核心价值是:
不是为了少写一个类文件,而是为了临时传入一段“行为”
3. 匿名内部类的典型使用场景
典型使用场景如下:
- 事件回调
- 事件监听
- 线程 Runnable
- 排序规则
下面主要讲后三种:
1. 事件监听
JButton btn = new JButton("Bell");
panel.add(btn);
btn.addActionListener(e -> System.out.println("knock knock"));
本质上需要传入一个 ActionListener 接口的实现类(这里已经用 lambda 简化)。
传统写法:
static class BellImpl implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("knock knock");
}
}
线程 Runnable 和排序规则也是同理,本质都是:传入一个行为
4. 从匿名内部类到 Lambda 表达式
虽然匿名内部类已经能解决“临时传递行为”的问题,但语法比较啰嗦。
下面通过排序来说明:
Arrays.sort(members, new Comparator<Member>() {
@Override
public int compare(Member o1, Member o2) {
return o1.getAge() - o2.getAge();
}
});
实际上真正有意义的只有:
return o1.getAge() - o2.getAge();
前面的 new Comparator、@Override 等只是语法“壳”。
👉 为了简化,引入 Lambda:
Arrays.sort(members, (o1, o2) -> o1.getAge() - o2.getAge());
Lambda 让代码更聚焦在“参数 + 行为”。
Lambda 的前提
并不是所有匿名内部类都可以用 lambda 简化,只有函数式接口可以:
@FunctionalInterface
interface Sleep {
void sleep();
}
👉 即:有且只有一个抽象方法
并且通过 @FunctionalInterface 注解来表示和验证是否是函数式接口
Lambda 的简化规则
- 参数类型可以省略
(o1, o2) -> ...
- 方法体只有一行时,可以省略
{}
(o1, o2) -> o1.getAge() - o2.getAge()
❗ 注意:此时不能写 return
- 多行时必须写
{}和return
(o1, o2) -> {
return o1.getAge() - o2.getAge();
}
- 只有一个参数时,括号可省略
5. lambda 的进一步简化:方法引用
当 Lambda 只是调用一个已有方法时:
(o1, o2) -> Member.compareByAge(o1, o2)
可以写成:
Member::compareByAge
1. 静态方法引用
如果某个 Lambda 表达式里只是调用一个静态方法,并且 "→" 前后参数的形式一致,就可以使用静态方法引用。
Arrays.sort(members, Member::compareByAge);
public class Member {
private String name;
private int age;
private String gender;
private double height;
public static int compareByAge(Member m1, Member m2){
return Integer.compare(m1.getAge(), m2.getAge());
}
}
匿名内部类:
Collections.sort(list, new Comparator<Member>() {
@Override
public int compare(Member o1, Member o2) {
return Member.compareByAge(o1, o2);
}
});
lambda:
(o1, o2) -> Member.compareByAge(o1, o2)
方法引用:
Member::compareByAge
👉 本质条件不是简单“参数一致”,而是:
函数式接口的方法签名可以匹配该方法
在 Java 里,一个方法的“签名”核心包括:
👉 参数列表 + 返回值类型(在这里也必须匹配)
2. 实例方法引用
原写法:
public int compareByHeight(Member m1, Member member)
👉 这个写法不太合理,因为没有使用 this
更推荐写法:
public int compareByHeight(Member other) {
return Double.compare(this.height, other.height);
}
使用:
Arrays.sort(members, Member::compareByHeight);
👉 这里的含义是:
- 第一个参数作为调用者(this)
- 第二个参数作为方法参数
6、构造器引用
如果 Lambda 只是创建对象:
Function<String, Member> f = name -> new Member(name);
可以简化为:
Function<String, Member> f = Member::new;
👉 本质:参数列表一致 + 返回值是新对象
⭐ 三种写法对比(从匿名内部类到方法引用)
同一个排序逻辑,可以用三种方式实现:
1️⃣ 匿名内部类
Arrays.sort(members, new Comparator<Member>() {
@Override
public int compare(Member o1, Member o2) {
return o1.getAge() - o2.getAge();
}
});
2️⃣ Lambda 表达式
Arrays.sort(members, (o1, o2) -> o1.getAge() - o2.getAge());
3️⃣ 方法引用(前提:已有比较方法)
Arrays.sort(members, Member::compareByAge);
👉 调用关系本质
这三种写法虽然形式不同,但底层调用是一样的:
Arrays.sort(...)
↓
Comparator.compare(o1, o2)
↓
具体比较逻辑(年龄比较)
⭐ 核心理解
- 匿名内部类:手动创建一个 Comparator 对象
- Lambda:简化 compare 方法的实现
- 方法引用:直接复用已有方法
👉 本质上,它们都在实现同一件事:
为 Comparator 提供 compare() 的实现(也就是“传递一个行为”)
总结
一句话总结
匿名内部类 → Lambda → 方法引用,本质都是在做一件事:传递行为
对比总结
| 写法 | 本质 | 特点 |
|---|---|---|
| 匿名内部类 | 创建一个行为对象 | 最灵活但最啰嗦 |
| Lambda | 简化实现方法 | 可读性好 |
| 方法引用 | 直接复用已有方法 | 最简洁但限制最多 |
- 匿名内部类:把行为“包成对象”
- Lambda:把行为“直接写出来”
- 方法引用:把行为“指向已有方法”
👉 三者只是写法不同,本质统一:
都是把一段行为作为参数传递给其他代码
补充
Lambda 在 JVM 层面是通过 invokedynamic 实现的,并不是简单的匿名内部类语法糖。
结语
如果从“行为传递”这个角度去理解:
- 排序、线程、回调这些 API 会变得更清晰
- Lambda 和方法引用也不再是单纯的语法技巧
👉 这也是 Java 8 一个非常重要的变化:
把行为当作数据进行传递