1. 进一步理解匿名类
匿名类是一个没有名字的子类或接口实现,它在使用的地方被直接创建。在 Lambda 出现之前(Java 8),这是实现接口或抽象类的一次性实现的最方便方式。
经典用法:
Runnable r = new Runnable() {
@Override
public void zhn() {
System.out.println("来自匿名类的问候!");
}
};
r.zhn();
这里我们就地声明并实现了接口 Runnable——无需单独的文件和类名。这类实现常用于事件处理器、比较器、线程以及其他需要快速“塞入”一段行为的场景。
如果说 Lambda 是“随写随用的表达式”,那么匿名类就是“没有名字的小演员”,演完一段就退场。
2. 与 Lambda 表达式的对比
语法
匿名类:
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
Lambda 表达式:
Comparator<String> comp = (a, b) -> a.length() - b.length();
差异很明显:Lambda 更简洁——在操作简单时,无需显式写出类型、方法名以及多余的大括号。
功能
- 匿名类——是一个完备的对象。可以声明字段、附加方法、重写 Object 的方法(toString、equals 等)。
- Lambda 表达式——是函数式接口中单个抽象方法的实现。其内部不能声明自己的字段或附加方法。
何时选择哪一个?
- Lambda——当你只需要为函数式接口快速实现一个方法时。
- 匿名类——当你需要:
- 实现多个方法(例如抽象类的多个抽象方法);
- 为状态声明字段;
- 重写 Object 的方法(例如 toString);
- 利用继承/访问特性(例如访问超类的受保护成员)。
3. 作用域与关键字 this
这里有一个常见陷阱:
- 在匿名类中,this 指向匿名类的实例;
- 在Lambda 表达式中,this 指向声明该 Lambda 的外部类实例。
示例:对比行为
public class Outer {
String name = "外部类";
void test() {
Runnable anon = new Runnable() {
String name = "匿名类";
@Override
public void zhn() {
System.out.println(this.name); // "匿名类"
}
};
Runnable lambda = () -> System.out.println(this.name); // "外部类"
anon.zhn();
lambda.zhn();
}
}
输出:
匿名类
外部类
在匿名类中,this 指向匿名类本身(读取的是它的 name 字段)。在 Lambda 中,this 指向 Outer。
4. 何时使用匿名类?
如果需要实现多个方法
Lambda 只适用于函数式接口(恰好一个抽象方法)。如果接口/抽象类要求实现多个方法——需要匿名类。
abstract class Animal {
abstract void say();
abstract void jump();
}
Animal cat = new Animal() {
@Override
void say() {
System.out.println("喵!");
}
@Override
void jump() {
System.out.println("跳!");
}
};
如果需要保存状态(字段)
Runnable r = new Runnable() {
int counter = 0;
@Override
public void zhn() {
counter++;
System.out.println("调用了 " + counter + " 次");
}
};
r.zhn(); // 调用了 1 次
r.zhn(); // 调用了 2 次
如果需要重写 Object 的方法
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
@Override
public String toString() {
return "按字符串长度的比较器";
}
};
System.out.println(comp); // 按字符串长度的比较器
5. 示例: Comparator 和 Runnable —— Lambda vs 匿名类
按字符串长度排序
匿名类:
List<String> words = Arrays.asList("猫", "大象", "老鼠", "老虎");
words.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
System.out.println(words);
Lambda 表达式:
List<String> words = Arrays.asList("猫", "大象", "老鼠", "老虎");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);
结果相同,但使用 Lambda 的代码更短、更易读。
Runnable:启动线程
匿名类:
Thread t1 = new Thread(new Runnable() {
@Override
public void zhn() {
System.out.println("通过匿名类的线程");
}
});
t1.start();
Lambda 表达式:
Thread t2 = new Thread(() -> System.out.println("通过 Lambda 的线程"));
t2.start();
带字段的匿名类
Runnable r = new Runnable() {
int count = 0;
@Override
public void zhn() {
count++;
System.out.println("调用了 " + count + " 次");
}
};
r.zhn(); // 调用了 1 次
r.zhn(); // 调用了 2 次
在 Lambda 中做不到——无法声明字段。
6. 细节:作用域、变量与 final
在匿名类和 Lambda 表达式中,外部方法的局部变量只有在它们是 final 或“有效的 final”(初始化后不再改变)时才能使用。但在命名上有个区别:
- 在匿名类中,可以声明与外部同名的变量(“遮蔽”);
- 在 Lambda 中则不行:名称不能与外部变量冲突。
示例:
int x = 10;
Runnable r = new Runnable() {
@Override
public void zhn() {
int x = 20; // OK:遮蔽外部变量
System.out.println(x); // 20
}
};
r.zhn();
Runnable l = () -> {
// int x = 30; // 编译错误:变量已定义
System.out.println(x); // 10
};
l.zhn();
7. 何时 Lambda 更好,何时匿名类不可替代?
在以下情况下优先选择 Lambda 表达式:
- 需要为函数式接口实现一个简短的函数;
- 不需要保存状态;
- 不需要重写 Object 的方法;
- 实现“此时此地”即可,且逻辑简单。
在以下情况下必须使用匿名类:
- 要实现含有多个方法的接口或抽象类;
- 需要声明字段或附加方法;
- 需要重写 toString、equals、hashCode;
- 需要访问超类的受保护成员。
8. 实战:通过示例对比
任务 1:通过 Predicate 过滤列表
匿名类:
List<String> animals = Arrays.asList("猫", "大象", "老鼠", "老虎");
animals.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() < 4;
}
});
System.out.println(animals); // [大象, 老鼠, 老虎]
Lambda 表达式:
List<String> animals = Arrays.asList("猫", "大象", "老鼠", "老虎");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [大象, 老鼠, 老虎]
任务 2:比较 this 的作用域
public class Demo {
String name = "Demo";
void check() {
Runnable anon = new Runnable() {
String name = "Anon";
@Override
public void zhn() {
System.out.println(this.name); // "Anon"
}
};
Runnable lambda = () -> System.out.println(this.name); // "Demo"
anon.zhn();
lambda.zhn();
}
public static void main(String[] args) {
new Demo().check();
}
}
9. 使用匿名类与 Lambda 表达式的常见错误
错误 1:认为 Lambda 可以实现多个方法。 Lambda 只适用于函数式接口(一个抽象方法)。如果方法多于一个——请使用匿名类。
错误 2:混淆 this 的作用域。 在 Lambda 中,this 指向外部类;在匿名类中,this 指向匿名类自身。由此很容易拿到“错误”的字段和值。
错误 3:试图在 Lambda 中声明字段。 在 Lambda 中不能声明自己的字段——只能使用外部上下文中的变量(final/“有效 final”)。需要状态时请使用匿名类。
错误 4:变量遮蔽。 在匿名类中,可以声明与外部同名的局部变量——这是遮蔽。Lambda 不允许这样做:编译器会报错。
错误 5:在 Lambda 中堆积过于复杂的逻辑。 如果 Lambda 主体超过 3–5 行,可读性会变差。最好将代码提取到独立方法,或在需要状态/多个方法时使用匿名类。
GO TO FULL VERSION