匿名内部类
一、内部类的概述:
当一个事物的内部还存在一个需要完整结构描述的部分,且该结构仅服务于外部事物时,推荐使用内部类。在Java中,可以将⼀个类定义在另⼀个 类或者⼀个⽅法的内部,前者称为内部类,后者称为外部类。内部类也是封装的⼀种体现。
1.1 基本语法
public class OutClass { // 外部类
class InnerClass { // 内部类
}
}
// OutClass是外部类
// InnerClass是内部类
1.2 注意事项
- 定义在
class花括号外部的类不是内部类
public class A {}
class B {} // A和B是两个独立类
二、内部类的分类
2.1 成员内部类
public class OutClass {
// 1.成员位置定义:未被static修饰 --->实例内部类
public class InnerClass1{
}
// 2.成员位置定义:被static修饰 ---> 静态内部类
static class InnerClass2{
}
public void method(){
// 3.⽅法中也可以定义内部类 ---> 局部内部类:⼏乎不⽤
class InnerClass5{
}
}
}
- 根据内部类定义的位置不同,一般分为以下几种:
| 类型 | 修饰符 | 是否有类名 | 学习 | 是否常用 |
|---|---|---|---|---|
| 1. 成员内部类 | 成员位置 | 有类名 | 了解 | 一般 |
| 2. 静态内部类 | 成员位置 | 有类名 | 了解 | 比较常用 |
| 3. 局部内部类 | 方法中 | 有类名 | 了解 | 少见 |
| 4. 匿名内部类 | 方法中 | 无类名 | 熟练掌握 | 最常用 |
2.1.1 静态内部类
public class OutClass {
public int a;
public static int b;
static class InnerClass {
public void methodInner() {
// 只能访问外部类静态成员
b = 200;
a = 100; // 编译错误
}
}
}
特点:
- 使用
static修饰 - 只能访问外部类的静态成员
- 无需外部类实例即可创建:
为什么创建静态内部类的对象时,不需要先创建外部类对象?”
这个是因为静态内部类本质上和外部类没有“对象上的从属关系,不像普通的非静态内部类那样必须依赖外部类的实例对象。
无需先new OutClass().new InnerClass(),而是直接像下面这样:new OutClass.InnerClass();
OutClass.InnerClass inner = new OutClass.InnerClass();
2.1.2实例内部类
public class OutClass {
public int a;
public static int b;
public int c = 1;
private int d = 1;
class InnerClass {
int e = 2;
public void methodInner() {
d = 400; // 访问外部类私有实例成员
a = 100; // 访问外部类实例成员
b = 200; // 访问外部类静态成员
// 访问内部类成员
c = 300;
OutClass.this.c = 400; // 访问外部类同名成员
}
}
}
特点:
- 必须通过外部类实例创建
- 可访问外部类所有成员,包括私有成员
- 访问同名成员时使用
外部类名.this.成员名:
OutClass.InnerClass inner = new OutClass().new InnerClass();
2.2 局部内部类
定义在方法体内的内部类,使用频率极低。
public class OutClass {
int a = 10;
public void method() {
int b = 20;
class InnerClass { // 局部内部类
public void print() {
System.out.println(a + " " + b);
}
}
new InnerClass().print();
}
}
特点:
- 仅在定义的方法体内有效
- 不能使用访问修饰符
- 编译后生成
外部类$数字内部类.class
2.3 匿名内部类
没有类名的即时类声明,适用于一次性使用的场景。
语法格式:
new 父类/接口() {
// 类体实现
};
接口实现示例:
interface Greeting {
void greet();
}
public class Test {
public static void main(String[] args) {
Greeting greeting = new Greeting() {
@Override
public void greet() {
System.out.println("Hello!");
}
};
greeting.greet();
}
}
对于匿名内部类,需要掌握其访问外部类的成员的规则,以及访问局部变量的规则
规则1:
1-匿名内部类访问外部类成员变量
1.实例方法里的匿名内部类:
可以访问外部类的所有成员变量,这点和成员内部类一样,包括实例变量,静态变量,私有变量
因为匿名内部类持有一个对外部类实例的隐式引用(OuterClass.this)所以可以访问实例成员
2.静态方法里的匿名内部类
可以访问静态成员变量,不能直接访问外部类的实例变量,如果想要访问实例变量必须通过外部实例引用。
3.访问外部类私有成员
Java编译器访问控制机制设计:内部类被视为外部类的一部分,所以可以访问私有成员
规则2:
2-变量捕获
访问匿名内部类所在的方法中的局部变量
- 局部变量不能有默认值(必须显式初始化)
Java不会为局部变量自动赋值默认值,不像类的成员变量那样
- 方法内部的变量不可定义为由访问修饰符修饰的,比如public int a;
因为方法内的变量属于局部变量。它的作用范围仅局限于方法体内,访问修饰符是用于类成员或者类的访问控制,不适用于局部变量。
- 方法内部变量不能用static修饰符修饰
因为static用于修饰类级别的成员,而方法内的变量是每次调用都会新建的,不属于类,也不属于类的成员,因此不能是静态的
- [重要]局部变量可以使用final修饰,含义是不可被重新赋值,或者是等效final,只要你不修改它的值,编译器会自动视为final修饰。
防止变量被意外修改
为什么Java要设定匿名内部类只能访问final或者等效final的局部变量呢?
public void test(){
int x = 10;
new Thread(()->{
System.out.print(x);//编译通过
}).start();
x = 20;//编译错误
}
Java的设计动机:
由于方法里面的变量存储在内存中的栈中,在方法结束之后就销毁了,而匿名内部类是创建在堆上的,因此里面的变量也存在在堆内存上,可能在方法运行完之后还在跑(比如在线程中)
因此Java的设计是,匿名内部类只获得了一份变量的‘’副本‘’也就是拷贝,Java编译器把x的值拷贝了一份到匿名内部类的字段里区,为了防止你后续又修改了x,会造成副本和原变量不一致,输出不可预测,引发逻辑混乱。所以Java要求不能修改被捕获的变量,必须是final或者等效final。
为什么方法结束了,线程还在运行?
因为线程是独立的执行路径,当你在方法里启动一个线程,线程会在新的执行路径(堆内存+线程栈)中继续运行,而不是随着方法结束就停掉。
捕获变量指的是:被局部内部类、匿名内部类或 Lambda 表达式访问到的、定义在方法外部(但仍是局部作用域内)的变量。
2.3.1 构造器和初始化
规则:
匿名类没有显式构造器,但可以通过 实例初始化块 初始化成员。
new Thread() {
private String message;
{ // 实例初始化块(类似构造器)
message = "初始化完成";
}
@Override
public void run() {
System.out.println(message); // 输出:初始化完成
}
};
2.3.2 继承与实现
规则:
匿名类 只能继承一个类 或 实现一个接口,不能同时继承和实现多个
// 正确:实现一个接口
new Runnable() {
@Override public void run() {}
};
// 正确:继承一个类
new Thread() {
@Override public void run() {}
};
// 错误:同时继承和实现(语法不允许)
// new Thread() implements Runnable { ... }
以下是一道匿名内部类的典型题目
下面 Java 代码的运行结果为()
public class Main {
private int num = 10;
public static void main(String[] args) {
Main main = new Main() {
@Override
public void printNum() {
System.out.println(num);
}
};
main.printNum();
}
public void printNum() {
System.out.println(num);
}
}
这道题考察了Java匿名内部类的语法规则和访问特性。
在该代码中存在编译错误,主要原因是在匿名内部类中访问外部类的私有成员变量num时违反了Java的内部类访问规则。
- 匿名内部类通过new Main(){}的方式创建,并重写了printNum()方法
- 在匿名内部类中的静态main方法直接访问外部类的私有成员实例变量num是不允许的
- 要访问外部类的私有成员,需要使用Main.this.num的形式,但由于是static方法我们仍然无法通过Main.this.num获取
- 另外,匿名内部类中的变量会隐藏外部类的同名变量
要修复这个编译错误,可以有以下方法:
(把 num 改为 static)
private static int num = 10; // 改为 static
这个题目很好地体现了Java内部类访问外部类成员的规则限制。
3665

被折叠的 条评论
为什么被折叠?



