内部类:将一个类定义置入另一个类定义中。
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
| public class OuterClass {
private String outerPrivateStr = "outerPrivateStr";
private void show(){ 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(){ } public void show(){ System.out.println("InnerClass show: " + OuterClass.this.outerPrivateStr); OuterClass.this.show(); } } }
public class TestInnerClass { public static void main(String[] args) { OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.show(); outerClass.accessInnerClassMemberNeedInstantiation(); } }
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(){ } public void show(){ 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{ } } }
public class TestMethodLocalInnerClass { public static void main(String[] args) { OuterMethodLocalClass outerMethodLocalClass = new OuterMethodLocalClass(); outerMethodLocalClass.showMethodLocalInnerClass(); } }
MethodLocalInnerClass: outPrivateStr MethodLocalInnerClass: finalStringShow OuterMethodLocalClass::show**InnerClass: methodLocalInnerClassPrivateStr
|
匿名内部类
定义
没有类名的,特殊的局部内部类,隐式的继承一个父类或者是实现某个接口。所谓匿名内部类就是在 new
一个对象时,改变类的方法。
Java
抽象类和接口(特殊的抽象类)是不能实例化的,但是抽象类或接口对象是可以指向实现类对象实例,最常见的方式就是使用匿名内部类充当这个实现类。
作用域
匿名内部类的作用域,它是一次性使用,用完即走。
访问范围
因匿名内部类为特殊的局部内部类,所以局部内部类的所有限制都对其生效。
特点
- 注意事项:匿名内部类不能有构造方法
- 实例化对象的格式
匿名内部类,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"); }
private Runnable mRunnable = new Runnable() { private String anonymousInnerClassPrivateStr = "anonymousInnerClassPrivateStr";
@Override public void run() { System.out.println("OuterAnonymousClass::AnonymousInnerClass " + OuterAnonymousClass.this.OuterPrivateStr); } }; }
public class TestAnonymousClass { public static void main(String[] args) { new OuterAnonymousClass().show(new Showable() { @Override public void show() { System.out.println("TestAnonymousClass::OuterAnonymousClass ::Showable::show"); } });
new OuterAnonymousClass(){ public void callParentProtectedMethod(){ super.protectedMethod(); } }.callParentProtectedMethod(); } }
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(){ }
public void showNormalMethod(){ 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); }
}
public class TestStaticNestedClass { public static void main(String[] args) { OuterStaticNestedClass.StaticNestedClass staticInnerClass = new OuterStaticNestedClass.StaticNestedClass(); staticInnerClass.showNormalMethod();
OuterStaticNestedClass.StaticNestedClass.showStaticMethod();
OuterStaticNestedClass outerStaticNestedClass = new OuterStaticNestedClass(); outerStaticNestedClass.showStaticNestedClassAccessControl(); } }
*::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
| public void showMethodLocalInnerClass(){ String cannotShow = "cannotShow"; final String finalStringShow = "finalStringShow"; class MethodLocalInnerClass{ ... public void show(){ ... System.out.println("MethodLocalInnerClass: " + finalStringShow); } } ... }
|
其中:内部类 MethodLocalInnerClass
使用了局部变量 finalStringShow
,这个就是闭包的例子。
同时根据注释也可以看出,这个局部变量必须是 final
的,否则会编译不过(Java 8
支持这个语法糖可以编过,8 以下都无法编译)。也就是说,Java
中实现闭包的概念需要使用 final
来修饰自由变量。
参考文档