前言
Java中的匿名类,Java8及以后。
涉及概念包括:等效final
,成员,变量,作用域。
匿名内部类(Anonymous Classes)
匿名内部类(匿名类)是一种在Java中用于创建临时性、一次性使用的类的特殊语法结构。它可以用来实现接口、继承类或创建临时对象等操作,通常在需要一个类的实例,但又不希望单独为之创建一个独立的类文件时使用。
匿名类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。它与局部类很相似,不同的是它没有类名,如果某个局部类你只需要用一次,就可以使用匿名类来表达。
匿名类通常用于以下场景:
实现接口: 通过匿名类实现一个接口,从而创建一个接口的实例。
继承类: 通过匿名类继承一个类,从而创建一个子类的实例。
创建临时对象: 在一些临时需要的情况下,可以使用匿名类来创建对象。
匿名类与局部类对比
1 | public class HelloWorldAnonymousClasses { |
该例中用局部类来初始化变量englishGreeting
,用匿类来初始化变量frenchGreeting
和spanishGreeting
,两种实现之间有明显的区别:
1)局部类EnglishGreeting
继承HelloWorld
接口,有自己的类名,定义完成之后需要再用new关键字实例化才可以使用;
2)frenchGreeting、spanishGreeting
在定义的时候就实例化了,定义完了就可以直接使用;
3)匿名类是一个表达式,因此在定义的最后用分号”;”结束。
匿名类的语法
匿名类表达式的通用语法如下:
1 | // 创建匿名类实例 |
其中的关键点包括:
SuperClassOrInterface
:匿名类可以继承一个类或实现一个接口,这个部分就是你要继承或实现的类或接口的类型。成员变量:如果需要,在匿名类内部可以定义成员变量,可以在构造器、初始化块以及方法中使用。
初始化块:如果需要,在匿名类内部可以定义初始化块,用来初始化成员变量。
方法重写:匿名类可以重写父类或接口中的方法。
returnType
:重写方法的返回类型。methodName()
:要重写的方法名。
匿名类对局部变量的访问规则,及对成员的声明及访问规则
与局部类一样,匿名类可以捕获变量,它们对外部域的局部变量具有相同的访问权限:
- 匿名类可以访问其外部类的成员。
- 匿名类无法访问其外部域内未声明为
final
或实际上未声明为final
的局部变量。 - 与嵌套类一样,匿名类中的类型(例如变量)声明会隐藏(shadow)外部域中具有相同名称的任何其他声明。
匿名类对于其成员也有与局部类相同的限制:
- 不能在匿名类中声明静态初始化块或成员接口。
- 匿名类可以具有静态成员,前提是它们是常量变量。
匿名类中可以声明以下内容:
- 字段
- 额外的方法(即使它们不实现父类的任何方法)
- 实例初始化块(Instance initializers)
- 局部类(Local classes)
但是,在匿名类中不能声明构造函数(constructors)。
匿名类访问外部类成员
一个有继承父类的匿名类示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47public class AnimalTest {
// 外部类成员变量
private String ANIMAL = "动物";
// 外部类成员方法
public void accessTest() {
System.out.println("匿名类访问其外部类方法");
}
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void printAnimalName() {
System.out.println(bird.name);
}
}
// 鸟类,匿名子类,继承自Animal类,可以覆写父类方法
Animal bird = new Animal("布谷鸟") {
public void printAnimalName() {
accessTest(); // 访问外部类成员方法
System.out.println(ANIMAL); // 访问外部类成员变量
super.printAnimalName();
}
};
public void print() {
bird.printAnimalName();
}
public static void main(String[] args) {
AnimalTest animalTest = new AnimalTest();
animalTest.print();
// 输出:
// 匿名类访问其外部类方法
// 动物
// 布谷鸟
}
}
可以看到,匿名类可以访问外部类的所有成员。事实上,他们甚至可以修改这些成员。
匿名类访问局部变量
匿名类与局部类对外部域局部变量的访问权限是相同的,先看下面局部类的例子:
1 | // author: SilenceZheng66 |
输出说明局部类可以访问外部类成员,也可以访问局部变量。但修改局部变量会发生什么?我们添加localValue = 10;
在对应注释下并执行,会报错:java: 从内部类引用的本地变量必须是最终变量或实际上的最终变量
。
这是因为局部类访问局部变量的先决条件是局部变量满足final
或等效final
。简单来说,该局部变量必须在初始化后不被修改,无论是否声明为final
。
匿名类对于局部变量的访问规则与局部类相同,如下。
1 | // author: SilenceZheng66 |
匿名类隐藏外部域声明
当在匿名类中声明具有与外部类相同名称的类型(例如变量)时,会发生隐藏(shadowing)现象。这意味着匿名类中的类型声明会覆盖外部类中具有相同名称的任何其他声明,从而隐藏外部类中的相应内容。
下面是一个示例,用于说明这一点:
1 | public class ShadowingExample { |
在匿名类中,我们可以通过 ShadowingExample.this.value
访问外部类的成员变量,因为匿名类中的局部变量 value
隐藏了外部类的局部变量 value
,但没有隐藏外部类的成员变量 value
。
参考
[1] https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html
[2] https://www.cnblogs.com/wuhenzhidu/p/anonymous.html
[3] https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html#shadowing
[4] https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
后记
首发于 silencezheng.top,转载请注明出处。