内部类 (Inner Class)
一、内部类的基本概念
1. 什么是内部类?
-
定义:在一个类的内部定义的另一个类。
-
位置 :类的五大成员之一(属性、方法、构造方法、代码块、内部类)。
-
设计思想 :内部类代表的事物是外部类的一部分,它们之间存在紧密的逻辑关系。内部类单独出现没有意义。
2. 内部类的访问特点
-
内部类 → 外部类 :可以直接访问外部类的所有成员(包括私有成员)。
-
外部类 → 内部类 :必须创建内部类对象才能访问内部类的成员。
java
public class Outer {
private String outerField = "外部类私有属性";
// 内部类
public class Inner {
public void show() {
// 内部类可以直接访问外部类的私有成员
System.out.println("访问外部类属性:" + outerField);
}
}
public void test() {
// 外部类要访问内部类,必须创建对象
Inner inner = new Inner();
inner.show();
}
}
二、内部类的分类
内部类主要有四种类型:
-
成员内部类 - 定义在成员位置
-
静态内部类 - 用
static修饰的成员内部类 -
局部内部类 - 定义在方法或代码块内部
-
匿名内部类 - 没有名字的特殊局部内部类
三、成员内部类
1. 基本特点
-
位置:定义在类的成员位置(与属性、方法同级)。
-
修饰符 :可以使用
public、protected、private、默认等访问修饰符。 -
访问:可以访问外部类的所有成员。
2. 获取成员内部类对象的两种方式
方式一:当内部类被 private 修饰时
java
public class Outer {
private String name = "外部类";
// 私有内部类
private class Inner {
public void show() {
System.out.println("内部类访问:" + name);
}
}
// 对外提供内部类对象的方法
public Inner getInnerInstance() {
return new Inner();
}
}
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.getInnerInstance(); // 通过方法获取
inner.show();
}
}
方式二:当内部类被非私有修饰时
java
public class Outer {
public String name = "外部类";
// 公共内部类
public class Inner {
public void show() {
System.out.println("内部类访问:" + name);
}
}
}
public class Test {
public static void main(String[] args) {
// 直接创建内部类对象(两步合一)
Outer.Inner inner = new Outer().new Inner();
// 或者分两步
Outer outer = new Outer();
Outer.Inner inner2 = outer.new Inner();
inner.show();
}
}
3. 成员变量重名问题
当内部类和外部类有重名的成员变量时:
java
public class Outer {
int num = 10; // 外部类成员变量
public class Inner {
int num = 20; // 内部类成员变量
public void show() {
int num = 30; // 局部变量
System.out.println(num); // 30 (局部变量,就近原则)
System.out.println(this.num); // 20 (内部类的num)
System.out.println(Outer.this.num); // 10 (外部类的num,重要!)
}
}
}
四、静态内部类
1. 基本特点
-
定义 :用
static修饰的成员内部类。 -
访问限制 :只能直接访问外部类的静态成员。
-
创建对象:不依赖于外部类对象,可以直接创建。
2. 创建与使用
java
public class Outer {
private static int staticNum = 100;
private int instanceNum = 200;
// 静态内部类
public static class StaticInner {
public void show() {
System.out.println("访问外部类静态变量:" + staticNum);
// System.out.println(instanceNum); // 错误!不能直接访问非静态成员
// 如果要访问非静态成员,需要创建外部类对象
Outer outer = new Outer();
System.out.println("通过对象访问:" + outer.instanceNum);
}
public static void staticShow() {
System.out.println("静态内部类的静态方法");
}
}
}
public class Test {
public static void main(String[] args) {
// 创建静态内部类对象(不需要外部类对象)
Outer.StaticInner inner = new Outer.StaticInner();
// 调用非静态方法
inner.show();
// 调用静态方法
Outer.StaticInner.staticShow();
}
}
五、局部内部类
1. 基本特点
-
位置 :定义在方法内部 或代码块内部。
-
作用域:类似于局部变量,只在定义它的方法或代码块内有效。
-
访问权限 :可以访问外部类的所有成员,以及所在方法的
final或事实上的final局部变量(JDK 8+)。
2. 使用示例
java
public class Outer {
private String outerField = "外部类属性";
public void method() {
final int localVar = 100; // 局部变量(final)
int effectivelyFinal = 200; // 事实上的final变量(只赋值一次)
// 局部内部类
class LocalInner {
public void show() {
System.out.println("访问外部类属性:" + outerField);
System.out.println("访问局部变量:" + localVar);
System.out.println("访问事实final变量:" + effectivelyFinal);
// effectivelyFinal = 300; // 如果在这里修改,会编译错误
}
}
// 只能在方法内部创建和使用
LocalInner inner = new LocalInner();
inner.show();
}
// 外部无法访问局部内部类
public void test() {
// LocalInner inner = new LocalInner(); // 编译错误!
}
}
六、匿名内部类(重点)
1. 基本概念
-
定义:没有名字的内部类,是局部内部类的特殊形式。
-
特点:
-
必须继承一个父类 或实现一个接口。
-
只能使用一次(创建对象的同时定义类)。
-
通常用于简化代码,特别是回调函数、事件监听等场景。
-
2. 基本格式
java
new 父类/接口() {
// 重写或实现方法
方法体
};
3. 本质分析
java
// 匿名内部类实际上做了三件事:
// 1. 定义一个匿名的子类/实现类(隐藏了类名)
// 2. 重写父类/接口的方法
// 3. 创建该匿名类的对象
4. 使用示例
示例1:基于接口的匿名内部类
java
// 接口
public interface Animal {
void eat();
}
public class Test {
public static void main(String[] args) {
// 传统方式:先定义实现类
// class Dog implements Animal {
// public void eat() {
// System.out.println("狗吃骨头");
// }
// }
// Animal dog = new Dog();
// 匿名内部类方式(一步到位)
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("匿名内部类:动物在吃东西");
}
};
animal.eat();
}
}
示例2:基于类的匿名内部类
java
// 父类
public class Person {
public void sayHello() {
System.out.println("你好,我是Person");
}
}
public class Test {
public static void main(String[] args) {
// 创建Person的匿名子类对象
Person person = new Person() {
@Override
public void sayHello() {
System.out.println("你好,我是匿名子类");
}
// 可以添加新方法(但外部无法直接调用,多态限制)
public void extraMethod() {
System.out.println("额外方法");
}
};
person.sayHello(); // 输出:你好,我是匿名子类
// person.extraMethod(); // 编译错误!Person类没有这个方法
}
}
5. 实际应用场景(最常用)
场景:作为方法参数
java
// 接口
public interface OnClickListener {
void onClick();
}
// 使用接口作为参数的方法
public class Button {
public void setOnClickListener(OnClickListener listener) {
// 模拟点击事件
listener.onClick();
}
}
public class Test {
public static void main(String[] args) {
Button button = new Button();
// 传统方式:需要先定义实现类
// class MyListener implements OnClickListener {
// @Override
// public void onClick() {
// System.out.println("按钮被点击了");
// }
// }
// button.setOnClickListener(new MyListener());
// 匿名内部类方式(简化代码)
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("按钮被点击了 - 使用匿名内部类");
}
});
// JDK 8+ Lambda表达式(更简化)
button.setOnClickListener(() -> {
System.out.println("按钮被点击了 - 使用Lambda");
});
}
}
场景:创建线程(经典示例)
java
public class ThreadDemo {
public static void main(String[] args) {
// 传统方式:实现Runnable接口
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1运行中...");
}
});
// Lambda表达式(JDK 8+)
Thread thread2 = new Thread(() -> {
System.out.println("线程2运行中...");
});
thread1.start();
thread2.start();
}
}
6. 匿名内部类的细节
细节1:调用特有方法
java
// 如果需要调用匿名内部类的特有方法,可以使用多态+强转
public interface Worker {
void work();
}
public class Test {
public static void main(String[] args) {
Worker worker = new Worker() {
@Override
public void work() {
System.out.println("在工作");
}
public void rest() {
System.out.println("在休息");
}
};
worker.work();
// worker.rest(); // 编译错误
// 但可以通过这种方式调用(不推荐,违反设计初衷)
Object obj = worker; // 可以转为Object
// 然后需要知道具体类型才能强转
}
}
细节2:访问外部变量
java
public class Test {
public static void main(String[] args) {
final int x = 10;
int y = 20; // JDK 8+ 自动视为final(如果后续不修改)
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(x); // 可以访问
System.out.println(y); // JDK 8+ 可以访问
// y = 30; // 如果在匿名内部类中修改y,会编译错误
}
};
// y = 30; // 如果在这里修改y,上面的匿名内部类会编译错误
r.run();
}
}
七、内部类总结对比
| 类型 | 位置 | 修饰符 | 访问外部类成员 | 创建对象 | 使用场景 |
|---|---|---|---|---|---|
| 成员内部类 | 类成员位置 | 各种访问修饰符 | 所有成员 | Outer.Inner obj = outer.new Inner() |
紧密关联,需要独立使用 |
| 静态内部类 | 类成员位置 | static + 访问修饰符 |
仅静态成员 | Outer.Inner obj = new Outer.Inner() |
工具类,不依赖实例 |
| 局部内部类 | 方法/代码块内部 | 无(类似局部变量) | 所有成员 + 局部final变量 | 只能在方法内部创建 | 方法内专用辅助类 |
| 匿名内部类 | 方法/代码块内部 | 无 | 所有成员 + 局部final变量 | new 父类/接口(){...} |
一次性使用,回调监听 |
八、最佳实践建议
-
优先考虑是否真的需要内部类 - 如果逻辑足够独立,可以设计为普通类。
-
成员内部类 - 当内部类需要独立使用,且与外部类关系紧密时使用。
-
静态内部类 - 当内部类不需要访问外部类实例时使用,性能更好。
-
匿名内部类 - 适用于一次性使用的回调、监听器等情况。
-
局部内部类 - 当某个类只在特定方法中有用时使用。
-
注意内存泄漏 - 内部类会隐式持有外部类的引用,可能影响垃圾回收。