Java 内部类

内部类:将一个类定义置入另一个类定义中。

Java 顶级类类名必须与文件名相同(大小写也必须一样),并且顶级类只能使用 public 或者不用访问控制符(default,包内可见)。在顶级类中新定义的类,都是内部类。
Java 内部类分四种:成员内部类、局部内部类、匿名内部类和静态内部类。

成员内部类

定义

定义在一个类的内部,但是没有 static 关键字修饰,像成员变量一样定义的类,即为成员内部类。

作用域

成员内部类作用域是整个外部类,类似成员变量。

访问范围

  • 成员内部类能够访问外部类的所有属性及方法(包含外部类的 private 成员)
  • 外部类对内部类实例化对象后(不能直接使用),通过该对象能够访问成员内部类的所有成员和方法(包含成员内部类的 private 成员)

实例化

成员内部类的实例化,必须先实例化外部类。通过外部类的对象,new 一个成员内部类对象。

1
2
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass inner = outerClass.new InnerClass();

示例

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
47
48
49
50
51
52
53
54
55
// 1. 成员内部类
public class OuterClass {

// 外部类私有成员变量
private String outerPrivateStr = "outerPrivateStr";

// 外部类私有方法
private void show(){
// 外部类不能直接访问内部类的成员
// InnerClass.this.innerPublicStr; //! Won't compile.
System.out.println("OuterClass show.");
}

public void accessInnerClassMemberNeedInstantiation(){
// 需要实例化后,外部类才能访问内部类的所有成员变量
InnerClass innerClass = new InnerClass();
System.out.println("OuterClass::access**MemberNeedInstantiation: "
+ innerClass.innerPrivateStr);
}

public class InnerClass{
public String innerPublicStr = "innerPublicStr";
private String innerPrivateStr = "innerPrivateStr";

public InnerClass(){
// Constructor.
}

public void show(){
// 成员内部类能够直接访问外部类的所有成员变量和方法
// 使用 OuterClass.this.outerPrivateStr
// 或者直接使用 outerPrivateStr
System.out.println("InnerClass show: "
+ OuterClass.this.outerPrivateStr);
OuterClass.this.show();
}
}
}

// 2. 测试
public class TestInnerClass {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
// 成员内部类的实例化,必须先实例化外部类
// 通过外部类的对象,new 一个成员内部类对象
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.show();
outerClass.accessInnerClassMemberNeedInstantiation();
}
}

// 3. 结果
InnerClass show: outerPrivateStr
OuterClass show.
OuterClass::accessInnerClassMemberNeedInstantiation: innerPrivateStr

局部内部类

定义

局部内部类指的是定义在一个方法或代码块中的类。因为在方法或代码块内部,所以和局部变量一样,不能有任何访问控制修饰符(public 也不行)来修饰局部内部类

作用域

局部内部类的作用域为方法或代码块内部,类似方法内的局部变量。

访问范围

  • 局部内部类在方法或代码块外是不可见的。所以只能在当前方法或代码块中对局部内部类实例化对象,并可以访问局部内部类所有的成员和方法(private 类型也可以)
  • 局部内部类可以访问外部类所有成员和方法
  • 局部内部类访问方法或代码块内的成员变量时,只能访问 final 类型变量(语法检查,jdk 1.8 及以上不必指定 final

在局部内部类内部,可以使用访问控制符修饰变量或方法,但是局部内部类整个作用域都在方法或代码块内,而这个范围是可以访问局部内部类所有成员和方法的,所以使用访问控制符没有任何意义。

示例

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
47
48
49
50
51
52
53
54
55
public class OuterMethodLocalClass {
private String outPrivateStr = "outPrivateStr";

public void showMethodLocalInnerClass(){
String cannotShow = "cannotShow";
final String finalStringShow = "finalStringShow";
// 定义局部内部类
class MethodLocalInnerClass{
// 使用访问控制符没有任何意义
private String methodLocalInnerClassPrivateStr
= "methodLocalInnerClassPrivateStr";

public MethodLocalInnerClass(){
// Constructor.
}

public void show(){
// 不能访问非 final 变量
// cannotShow. //Won't compile.
System.out.println("MethodLocalInnerClass: "
+ OuterMethodLocalClass.this.outPrivateStr);
System.out.println("MethodLocalInnerClass: "
+ finalStringShow);
}
}

// 实例化局部内部类,并能访问所有成员和方法
MethodLocalInnerClass methodLocalInnerClass
= new MethodLocalInnerClass();
methodLocalInnerClass.show();
System.out.println("OuterMethodLocalClass::show**InnerClass: "
+ methodLocalInnerClass.methodLocalInnerClassPrivateStr);
}

{
// 代码块中,局部内部类
class CodeBlockInnerClass{
//
}
}
}

// 2. Test
public class TestMethodLocalInnerClass {
public static void main(String[] args) {
OuterMethodLocalClass outerMethodLocalClass
= new OuterMethodLocalClass();
outerMethodLocalClass.showMethodLocalInnerClass();
}
}

// 3. 结果
MethodLocalInnerClass: outPrivateStr
MethodLocalInnerClass: finalStringShow
OuterMethodLocalClass::show**InnerClass: methodLocalInnerClassPrivateStr

匿名内部类

定义

没有类名的,特殊的局部内部类,隐式的继承一个父类或者是实现某个接口。所谓匿名内部类就是在 new 一个对象时,改变类的方法。
Java 抽象类和接口(特殊的抽象类)是不能实例化的,但是抽象类或接口对象是可以指向实现类对象实例,最常见的方式就是使用匿名内部类充当这个实现类。

作用域

匿名内部类的作用域,它是一次性使用,用完即走。

访问范围

因匿名内部类为特殊的局部内部类,所以局部内部类的所有限制都对其生效。

特点

  • 注意事项:匿名内部类不能有构造方法
  • 实例化对象的格式
    1
    2
    3
    new 类名或接口名(){
    重写方法;
    }; //注意分号

匿名内部类,new 出来一个对象,该对象可以直接调用匿名内部类的方法。

作用

  • 重写或实现方法
    当仅仅需要重写类方法,或者实现抽象类/接口的方法时,使用匿名内部类使代码变得很简洁。
  • 重新定义新方法,调用外部类的 protected 方法

示例

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
47
48
49
50
51
52
53
54
55
public class OuterAnonymousClass {

public void show(Showable showable){
showable.show();
}

protected void protectedMethod(){
System.out.println("OuterAnonymousClass::protectedMethod");
//mRunnable.run();
}

// 使用匿名内部类,作为接口或者抽象类的实现类
// new 一个匿名内部类的标准格式
private Runnable mRunnable = new Runnable() {
// 匿名内部类中的成员变量毫无用处
private String anonymousInnerClassPrivateStr
= "anonymousInnerClassPrivateStr";

@Override
public void run() {
// 可以访问外部类所有成员和方法
System.out.println("OuterAnonymousClass::AnonymousInnerClass "
+ OuterAnonymousClass.this.OuterPrivateStr);
}
};
}

// 2. Test
public class TestAnonymousClass {
public static void main(String[] args) {
// Function 1: 匿名内部类
// 1. new 一个 OuterAnonymousClass 实例,调用 show 方法
// 2. new 一个 Showable 实例,并实现该接口的 show 方法
new OuterAnonymousClass().show(new Showable() {
@Override
public void show() {
System.out.println("TestAnonymousClass::OuterAnonymousClass
::Showable::show");
}
});

// Function 2: 匿名内部类调用 protected 方法
new OuterAnonymousClass(){
// 重新定义新方法,调用类的 protected 方法
public void callParentProtectedMethod(){
super.protectedMethod();
}
// 定义的新方法,只能这个匿名类实例调用
}.callParentProtectedMethod();
}
}

// 3. 结果
TestAnonymousClass::OuterAnonymousClass::Showable::show
OuterAnonymousClass::protectedMethod

静态内部类

定义

必须以 static 关键字标注类名,只能修饰成员内部类

作用域

静态内部类作用域为全局,并不依赖外部类实例,可以直接访问和使用。

特点

  • 嵌套类
    静态内部类在 oracle 中被定义为 static nested classes,即嵌套类;而其他为内部类 inner classes,是平行类。
  • 创建对象
    创建嵌套类对象,并不需要外部类实例化对象,静态类直接使用可以将其看成静态成员。

访问范围

  • 只能访问外部类中的静态的成员变量或者是静态的方法
  • 外部类访问静态内部类成员变量不受限制

示例

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class OuterStaticNestedClass {

public String outPublicStr = "outPublicStr";
private static String outPrivateStaticStr = "outPrivateStaticStr";

public static class StaticNestedClass{
private String staticNestedClassPrivateStr
= "staticNestedClassPrivateStr";
public String staticNestedClassPublicStr
= "staticNestedClassPublicStr";
public static String staticNestedClassPublicStaticStr
= "staticNestedClassPublicStaticStr";

public StaticNestedClass(){
//Constructor.
}

public void showNormalMethod(){
// outPublicStr //!Won't compile.
System.out.println("*::StaticNestedClass::showNormalMethod "
+ outPrivateStaticStr);
}

public static void showStaticMethod(){
System.out.println("*::StaticNestedClass::showStaticMethod "
+ outPrivateStaticStr);
System.out.println("*::StaticNestedClass::showStaticMethod "
+ staticNestedClassPublicStaticStr);
}
}

public void showStaticNestedClassAccessControl(){
StaticNestedClass staticNestedClass = new StaticNestedClass();
System.out.println("*::showStatic*AccessControl::StaticNestedClass "
+ staticNestedClass.staticNestedClassPrivateStr);
System.out.println("*::showStatic*AccessControl::StaticNestedClass "
+ staticNestedClass.staticNestedClassPublicStr);
System.out.println("*::showStatic*AccessControl::StaticNestedClass "
+ StaticNestedClass.staticNestedClassPublicStaticStr);
}

}

// 2. Test
public class TestStaticNestedClass {
public static void main(String[] args) {
// 1. StaticNestedClass: object.
OuterStaticNestedClass.StaticNestedClass staticInnerClass
= new OuterStaticNestedClass.StaticNestedClass();
staticInnerClass.showNormalMethod();

// 2. StaticNestedClass: static method.
OuterStaticNestedClass.StaticNestedClass.showStaticMethod();

// 3. OuterStaticNestedClass: object.
OuterStaticNestedClass outerStaticNestedClass
= new OuterStaticNestedClass();
outerStaticNestedClass.showStaticNestedClassAccessControl();
}
}

// 3.结果
*::StaticNestedClass::showNormalMethod outPrivateStaticStr
*::StaticNestedClass::showStaticMethod outPrivateStaticStr
*::StaticNestedClass::showStaticMethod staticNestedClassPublicStaticStr
*::showStatic*AccessControl::StaticNestedClass staticNestedClassPrivateStr
*::showStatic*AccessControl::StaticNestedClass staticNestedClassPublicStr
*::showStatic*AccessControl::StaticNestedClass staticNested*PublicStaticStr

内部类持有外部类的引用

反编译内部类

  • 成员内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class OuterClass$InnerClass
    {
    ...
    // 持有外部类引用
    public OuterClass$InnerClass(OuterClass this$0) {}

    public void show()
    {
    System.out.println("InnerClass show: " +
    OuterClass.access$100(this.this$0));
    OuterClass.access$200(this.this$0);
    }
    }
  • 局部内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class OuterMethodLocalClass$1MethodLocalInnerClass
    {
    ...
    // 持有外部类引用
    public OuterMethodLocalClass$1MethodLocalInnerClass(
    OuterMethodLocalClass this$0) {}

    public void show()
    {
    System.out.println("MethodLocalInnerClass: "
    + OuterMethodLocalClass.access$000(this.this$0));
    System.out.println("MethodLocalInnerClass: finalStringShow");
    }
    }
  • 匿名内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class OuterAnonymousClass$1
    implements Runnable
    {
    ...
    // 持有外部类引用
    OuterAnonymousClass$1(OuterAnonymousClass this$0) {}

    public void run()
    {
    System.out.println("OuterAnonymousClass::AnonymousInnerClass "
    + OuterAnonymousClass.access$000(this.this$0));
    }
    }
  • 静态内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class OuterStaticNestedClass$StaticNestedClass
    {
    private String staticNestedClassPrivateStr
    = "staticNestedClassPrivateStr";
    public String staticNestedClassPublicStr
    = "staticNestedClassPublicStr";
    public static String staticNestedClassPublicStaticStr
    = "staticNestedClassPublicStaticStr";

    public void showNormalMethod()
    {
    System.out.println("OuterStaticNestedClass::StaticNestedClass
    ::showNormalMethod " + OuterStaticNestedClass.access$000());
    }

    public static void showStaticMethod()
    {
    System.out.println("OuterStaticNestedClass::StaticNestedClass
    ::showStaticMethod " + OuterStaticNestedClass.access$000());
    System.out.println("OuterStaticNestedClass::StaticNestedClass
    ::showStaticMethod " + staticNestedClassPublicStaticStr);
    }
    }

小结

通过反编译内部类对应的 class 文件,可以看出内部类在构造方法中,传入了外部类的引用 this$0,内部类在访问外部类的成员或方法时,都需要传递该参数 this.this$0,只有静态内部类例外。这也可看出为什么静态内部类在实例化时不需要外部类实例化,而其他内部类在实例化时必须先实例化外部类了。
换句话说:静态内部类不持有外部类对象的引用,而其他内部类都会持有

内部类虽然和外部类写在同一个文件中,但是编译完成后会生成各自的 class 文件,编译过程中:

  • 编译器自动为非静态内部类添加一个成员变量,这个成员变量的类型和外部类的类型相同,这个成员变量就是指向外部类对象的引用
  • 编译器自动为非静态内部类的构造方法添加一个参数,参数的类型是外部类的类型,这个参数为内部类中添加的成员变量赋值
  • 在调用非静态内部类的构造函数初始化内部类对象时,会默认传入外部类的引用

总结

  • 作用域
    四种内部类作用域不同,根据作用域范围大小:静态内部类 > 成员内部类 > 局部内部类 > 匿名内部类。通常我们可以根据需求(作用域)来选择使用哪种内部类。
  • 访问范围
    在外部类整个类内部这个范围内,是不受访问控制符限制的,即 private 变量外部类和内部类是可以相互直接访问到的。所以内部类中通常会省略访问控制符。
  • 生成的 class 文件
    内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类。每个类编译后都会生成一个 .class 文件,内部类生成的对应文件格式为:外部类$内部类.class,例如:OuterClass$Inner.class
  • static 关键字
    static 关键字只能出现在静态内部类,不能出现在其他内部类中,除非是常量 static final。非静态内部类需要引用外部类的对象,如果有 static 成员,意味着外部类不需要创建实例对象,所以这是不允许的。
  • 引用外部类的成员
    格式:OuterClass.this.****** 表示成员变量,或者直接使用 ***

内部类的作用

  • 内部类能很好的聚合代码
  • 内部类可以很好的实现隐藏,通过访问控制符控制是否能被外部类访问
  • 内部类可以隐藏接口的实现细节,而只需对外提供这个接口就行
  • 实现多重继承,使用多个内部类分别继承,达到多重继承的目的

静态内部类的设计意图

首先它是内部类,所以拥有内部类的一些便捷性,外部类可以访问静态内部类的所有成员变量和方法。但是因为静态属性,静态内部类不持有外部类引用,和外部类平级。由静态内部类的角度看外部类,可以认为是独立的两个类。静态内部类适合于当做内部类使用但是又不依赖外部类。

闭包和局部内部类

闭包:是指可以包含自由(未绑定到特定对象)变量的代码块;这些自由变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。闭包是动态语言中的概念,并不适合 Java 这种静态语言。
闭包允许将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。闭包允许我们创建函数指针,并把它们作为参数传递(在 JAVA 中,闭包是通过“接口+内部类”实现)。
参考上面局部内部类中的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// OuterMethodLocalClass.java
public void showMethodLocalInnerClass(){
String cannotShow = "cannotShow";
final String finalStringShow = "finalStringShow";
// 定义局部内部类
class MethodLocalInnerClass{
...
public void show(){
// 不能访问非 final 变量
// cannotShow. //Won't compile.
...
System.out.println("MethodLocalInnerClass: "
+ finalStringShow);
}
}
...
}

其中:内部类 MethodLocalInnerClass 使用了局部变量 finalStringShow,这个就是闭包的例子。
同时根据注释也可以看出,这个局部变量必须是 final 的,否则会编译不过(Java 8 支持这个语法糖可以编过,8 以下都无法编译)。也就是说,Java 中实现闭包的概念需要使用 final 来修饰自由变量。

参考文档

0%