
概述
内部类是声明在另一个类的内部的类。本质上,它是一个被"嵌套"在外部类中的类。
当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述,而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构B最好使用内部类。总的来说,遵循高内聚、低耦合的面向对象开发原则。
内部类的分类
| 类型 | 所在位置 | 是否依赖外部类对象 | 特点 |
|---|---|---|---|
| 成员内部类 | 类中,方法外 | 依赖 | 最常见,可访问外部类成员 |
| 静态内部类 | 类中,方法外,带 static | 不依赖 | 不持有外部类引用,类似外部类的静态成员 |
| 局部内部类 | 方法内 | 依赖 | 方法内定义,仅方法内可见 |
| 匿名内部类 | 局部类的一种特殊形式 | 依赖 | 用来临时创建某个类或接口的对象(一次性使用) |
(静态)成员内部类
如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类。
java
[修饰符] class 外部类 {
[其他修饰符] [static] class 内部类 {
}
}
- 成员内部类作为
类的成员的角色:- 和外部类不同,内部类还可以声明为
private或protected; - 可以调用外部类的结构。(⚠️在静态内部类中不能使用外部类的非静态成员)
- 和外部类不同,内部类还可以声明为
- 成员内部类作为
类的角色:- 可以在内部定义属性、方法、构造器等结构;
- 可以继承父类和实现父接口,和外部类的父类和父接口无关;
- 可以声明为抽象类 ,因此可以被其它的内部类继承;
- 可以声明为
final的,表示不能被继承; - 编译后生成
OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意点
- 外部类访问成员内部类的成员,需要
内部类.成员或内部类对象.成员的方式; - 成员内部类可以直接使用外部类的所有成员,包括私有的数据;
实例化
- 非静态成员内部类
和外部类对象绑定:内部类对象必须依赖外部类对象。外部类可通过 new Inner() 创建内部类对象(必须有外部类对象前缀)。
java
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show();
- 静态成员内部类
不依赖外部类对象(不持有外部类实例)。可以访问外部类的静态成员,但不能访问非静态成员。
java
Outer.Inner inner = new Outer.Inner();
inner.show();
局部(匿名)内部类
局部内部类是定义在方法内部、代码块内部或构造器内部的类。作用域仅限于该局部范围内,外部无法直接访问。
java
// 非匿名
[修饰符] class 外部类 {
[修饰符] 返回值类型 方法名([形参列表]) {
[final/abstract] class 内部类 {
}
}
}
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
- 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类。
- 不能使用访问修饰符,因为它是局部变量级别的类型。
- 作用域仅限于该局部范围内,外部无法直接访问。
- 可以访问外部类成员,包含静态成员。
- 可以访问方法中的局部变量,但要求变量为 final 或 effectively final。Java 8 之后局部变量无需显式声明 final,只要它不被重新赋值(effectively final)即可。原因:局部变量属于栈内存,方法结束后销毁,但局部内部类对象可能还活着,因此 JVM 会复制一份局部变量到内部类中,需要保证不变性。
- 局部内部类可以定义 static 成员。
java
public class Outer {
private String name = "OuterName";
private static String a = "oa";
public void doWork() {
int count = 5; // effectively final
class Worker {
static int x = 0;
public void execute() {
x = 1;
System.out.println("访问外部类成员: " + name);
System.out.println("访问外部类static成员: " + a);
System.out.println("访问方法局部变量: " + count);
}
}
Worker w = new Worker();
w.execute();
}
public static void main(String[] args) {
new Outer().doWork();
}
}
匿名内部类是一种没有名字的局部内部类,通常用来简化代码,尤其在需要临时创建某个类的实例(通常是接口或抽象类)时使用。
java
父类/接口 类型 变量 = new 父类/接口() {
// 重写方法
};
传统方式:需要写一个单独的类文件 / 内部类实现一个接口。
java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Running...");
}
}
然后使用
java
Runnable r = new MyRunnable();
使用匿名内部类,节省代码、逻辑更集中,不用专门创建一个新类。
java
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Running...");
}
};
匿名内部类最常见的用途就是实现接口。
java
interface Greeting {
void sayHello();
}
public class Main {
public static void main(String[] args) {
Greeting g = new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello from anonymous class!");
}
};
g.sayHello();
///// or
new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello from anonymous class!");
}
}.sayHello();
}
}
匿名内部类的对象作为实参。
java
interface A {
void method();
}
public class Test {
public static void test(A a) {
a.method();
}
public static void main(String[] args) {
test(new A() {
@Override
public void method() {
System.out.println("aaaa");
}
});
}
}
java
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程运行");
}
}).start();