一、概述
Java 内部类是定义在其他类内部的类,具有一些特殊的特性和用途。以下是 Java 内部类的主要特性和使用。
二、内部类的四种类型
Java 内部类分为四种类型,其中两种是嵌套类,分为静态嵌套类和非静态嵌套类(成员内部类)。
1、非静态内部类(成员内部类)
(1)说明
-
依赖外部类的实例:非静态内部类与外部类的实例关联,可以访问外部类的实例变量和方法(包括私有实例变量和方法)。
-
不能定义静态成员:不允许在非静态内部类中定义静态变量和静态方法,以维持一致性和可读性。
(2)案例
public class OuterClass1 {
private int outerField = 1;
private void outerMethod() {
}
public class InnerClass {
void method() {
// 1、可以访问外部类的实例变量和实例方法
System.out.println(outerField);
outerMethod();
}
// 2、不能定义静态变量、静态方法
/*static int innerStaticField;
static void innerStaticMethod() {
}*/
}
public static void main(String[] args) {
OuterClass1 outerClass1 = new OuterClass1();
InnerClass innerClass = outerClass1.new InnerClass();
}
}
(3)原理
- 编译后会生成
OuterClass1.class
和OuterClass1$InnerClass.class
两个字节码文件。 OuterClass1$InnerClass.class
构造函数中有一个参数类型为OuterClass1
,也就是说在内部类实例化时,就会将外部类的实例对象传入,并且存储到内部类的实例变量中,这就解释了内部类是如果访问外部类的成员变量和成员方法的。- 在
OuterClass1.class
中有2个方法access$000
和access$100
,当内部类需要访问外部类的私有变量和方法时,就是通过这2个方法进行访问的。
Ⅰ、OuterClass1.class
Compiled from "OuterClass1.java"
public class work.vcloud.dc.controller.test.OuterClass1 {
public work.vcloud.dc.controller.test.OuterClass1();
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field outerField:I
9: return
public static void main(java.lang.String[]);
Code:
0: new #4 // class work/vcloud/dc/controller/test/OuterClass1
3: dup
4: invokespecial #5 // Method "<init>":()V
7: astore_1
8: new #6 // class work/vcloud/dc/controller/test/OuterClass1$InnerClass
11: dup
12: aload_1
13: dup
14: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class;
17: pop
18: invokespecial #8 // Method work/vcloud/dc/controller/test/OuterClass1$InnerClass."<init>":(Lwork/vcloud/dc/controller/test/OuterClass1;)V
21: astore_2
22: return
static int access$000(work.vcloud.dc.controller.test.OuterClass1);
Code:
0: aload_0
1: getfield #2 // Field outerField:I
4: ireturn
static void access$100(work.vcloud.dc.controller.test.OuterClass1);
Code:
0: aload_0
1: invokespecial #1 // Method outerMethod:()V
4: return
}
Ⅱ、OuterClass1$InnerClass.class
Compiled from "OuterClass1.java"
class work.vcloud.dc.controller.test.OuterClass1$InnerClass {
final work.vcloud.dc.controller.test.OuterClass1 this$0;
work.vcloud.dc.controller.test.OuterClass1$InnerClass(work.vcloud.dc.controller.test.OuterClass1);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lwork/vcloud/dc/controller/test/OuterClass1;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
void method();
Code:
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #1 // Field this$0:Lwork/vcloud/dc/controller/test/OuterClass1;
7: invokestatic #4 // Method work/vcloud/dc/controller/test/OuterClass1.access$000:(Lwork/vcloud/dc/controller/test/OuterClass1;)I
10: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
13: aload_0
14: getfield #1 // Field this$0:Lwork/vcloud/dc/controller/test/OuterClass1;
17: invokestatic #6 // Method work/vcloud/dc/controller/test/OuterClass1.access$100:(Lwork/vcloud/dc/controller/test/OuterClass1;)V
20: return
}
2、静态内部类
-
独立于外部类:静态内部类不依赖于外部类的实例,可以在没有创建外部类实例的情况下使用。
-
允许定义静态成员:可以在静态内部类中定义静态变量和静态方法,与普通类类似。
public class OuterClass2 {
private static int outerField = 1;
private static void outerMethod() {
}
static class InnerClass {
void method() {
// 1、可以访问外部类的静态变量和静态方法
System.out.println(outerField);
outerMethod();
}
// 2、可以定义静态变量、静态方法
static int innerStaticField;
static void innerStaticMethod() {
}
}
public static void main(String[] args) {
System.out.println(InnerClass.innerStaticField);
InnerClass.innerStaticMethod();
}
}
3、局部内部类
(1)说明
-
定义在方法内部:局部内部类是定义在方法内部的类,其作用域仅限于包含它的方法。
-
可以访问方法的局部变量,但要求局部变量为 final 或 effectively final。
(2)案例
public class OuterClass3 {
public void outerMethod() {
String str = "abc";
final String methodField = str;
class InnerClass {
void method() {
// 1、可以访问外部方法中的变量,但是变量必须是final修饰的(jdk 1.8之后会自动添加final修饰的)
System.out.println(methodField);
}
}
new InnerClass().method();
}
}
(3)原理
编译后会生成 OuterClass3.class
和 OuterClass3$InnerClass.class
两个字节码文件。
Ⅰ、OuterClass3.class
Compiled from "OuterClass3.java"
public class OuterClass3 {
public OuterClass3();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void outerMethod();
Code:
0: ldc #2 // String abc
2: astore_1
3: aload_1
4: astore_2
5: new #3 // class OuterClass3$1InnerClass
8: dup
9: aload_0
10: aload_2
11: invokespecial #4 // Method OuterClass3$1InnerClass."<init>":(LOuterClass3;Ljava/lang/String;)V
14: invokevirtual #5 // Method OuterClass3$1InnerClass.method:()V
17: return
}
Ⅱ、OuterClass3$1InnerClass.class
Compiled from "OuterClass3.java"
class OuterClass3$1InnerClass {
final java.lang.String val$methodField;
final OuterClass3 this$0;
OuterClass3$1InnerClass();
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LOuterClass3;
5: aload_0
6: aload_2
7: putfield #2 // Field val$methodField:Ljava/lang/String;
10: aload_0
11: invokespecial #3 // Method java/lang/Object."<init>":()V
14: return
public void method();
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #2 // Field val$methodField:Ljava/lang/String;
7: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
}
我们看下 OuterClass3$InnerClass.class
的构造器字节码指令。
aload_0
(指令0):将局部变量0的值加载到操作数栈中。这个值是当前对象引用(即this
)。aload_1
(指令1):将局部变量1的值加载到操作数栈中。这个值通常是外部类OuterClass3
的引用。putfield
(指令2):将操作数栈顶的值(即局部变量1中的OuterClass3
引用)赋值给当前对象(即this$0
字段)。#1
表示常量池中的索引,用于查找字段。aload_0
(指令5):再次将局部变量0的值加载到操作数栈中。这个值是当前对象引用(即this
)。aload_2
(指令6):将局部变量2的值加载到操作数栈中。这个值通常是外部传递的字符串参数。putfield
(指令7):将操作数栈顶的值(即局部变量2中的字符串引用)赋值给当前对象的val$methodField
字段。#2
表示常量池中的索引,用于查找字段。aload_0
(指令10):再次将当前对象引用加载到局部变量0中。invokespecial
(指令11):调用java/lang/Object
类的构造函数,执行对象的初始化。
通过上面的指令说明,可以看出内部类访问外部方法的局部变量,其实就是将方法的局部变量,通过内部类构造器传入后,保存在了内部类实例的属性 val$methodField
中。
为什么要使用 final
修饰局部变量呢?
现在我们从语义上来理解下Java设计者的考虑:假如传递到匿名内部类的局部变量,不加 final
修饰,那么意味着局部变量可以改变,这就意味着匿名内部类里面值的变更和外部的变量的变更不能同步,,虽然内部类持有的是局部变量值的拷贝,但是语义应该保持一致,语义保持一致的前提是值要能同步,因为java
编译器的设计无法提供两边同步变更的机制,所以直接锁死,不允许内外变更。
4、匿名内部类
-
通常用于创建实现某接口或继承某类的临时对象,不需要单独的类定义。
-
常见于事件处理和线程创建等场景。
public class OuterClass4 {
public static void main(String[] args) {
final int methodField = 1;
new Thread(new Runnable() {
@Override
public void run() {
// 1、可以访问外部方法中的变量,但是变量必须是final修饰的(jdk 1.8之后会自动添加final修饰的)
System.out.println(methodField);
}
});
}
}
三、总结
内部类可以增加封装性、组织性和代码复用性。它们允许将相关的类放在一起,并隐藏一些实现细节。
总之,Java 内部类提供了一种将类组织在一起的机制,以及在某些情况下实现更优雅和灵活的设计。使用内部类时,需要根据具体需求选择不同类型的内部类,并理解它们的特性和限制。