内部类概述
一个类可以放在另一个类的内部,称为内部类,相对而言,包含它的类称为外部类。
一般而言,内部类与包含它的外部类有比较密切的关系,与其他类关系不大,定义在类内部,可以实现对外部完全隐藏,从而有更好的封装性,代码实现上也往往更为简洁。
不过,内部类只是 Java 编译器的概念,对于 Java 虚拟机而言是不知道内部类这回事的,内部类最后都会被编译为一个独立的类,生成一个独立的字节码文件。
静态成员内部类
静态成员内部类定义在类的内部,与其他成员同级,带有 static
修饰符。
静态成员内部类除了位置放在其他类的内部外,与独立的类差别不大,可以有静态变量、静态方法、实例变量、实例方法、构造方法等。
静态成员内部类可以访问外部类的类变量和类方法,但不可以访问实例变量和实例方法。
在类内部,可以直接使用静态成员内部类。
在类外部,除了私有的外,可以通过 外部类.静态成员内部类 的方式使用。
public class Outer {
private static int shared = 100;
public static class StaticInner {
public void innerMethod(){
System.out.println("inner" + shared);
}
}
public void test(){
StaticInner si = new StaticInner();
si.innerMethod();
}
}
静态内部类是如何实现的,上述类编译后会生成两个类::一个是 Outer,另一个是 Outer$StaticInner。
编译后的代码大概如下:
public class Outer {
private static int shared = 100;
public void test(){
Outer$StaticInner si = new Outer$StaticInner();
si.innerMethod();
}
static int access$0(){
return shared;
}
}
public class Outer$StaticInner {
public void innerMethod() {
System.out.println("inner " + Outer.access$0());
}
}
内部类访问了外部类的一个私有静态变量 shared,而私有变量是不能被类外部访问的,Java 编译时会自动为 Outer生成一个非私有的访问方法 access$0,返回该私有静态变量 shared 。
普通成员内部类
普通成员内部类相比静态成员内部类,依赖实例,不可以定义静态变量和方法。
普通成员内部类可以直接访问外部类的静态和实例成员。
如果存在同名的情况,普通成员内部类可以通过外部类.this.xxx
的方式引用外部类的实例变量和方法,比如 Outer.this. action()
。
在类内部,可以直接使用普通成员内部类,与静态内部类不同,成员内部类对象总是与一个外部类对象相连。
在类外部,不能直接通过new Outer.Inner()
的方式创建对象,而是要先将创建一个外部类对象,再通过外部类的实例对象创建:外部类对象.new 内部类()
。
public class Test1 {
public static void main(String[] args) {
Outer outer = new Outer();
// 不建议,内部类是为了封装
Outer.Inner inner = outer.new Inner();
inner.print();
outer.innerPrint();
}
}
public class Test1 {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.innerMethod();
}
}
class Outer {
private int a = 100;
private void action(){
System.out.println("action");
}
public class Inner {
public void innerMethod(){
System.out.println("outer a " +a);
Outer.this.action();
}
}
public void test(){
Inner inner = new Inner();
inner.innerMethod();
}
}
编译后的代码大概如下:
public class Outer {
private int a = 100;
private void action() {
System.out.println("action");
}
public void test() {
Outer$Inner inner = new Outer$Inner(this);
inner.innerMethod();
}
static int access$0(Outer outer) {
return outer.a;
}
static void access$1(Outer outer) {
outer.action();
}
}
public class Outer$Inner {
final Outer outer;
public Outer$Inner(Outer outer){
ths.outer = outer;
}
public void innerMethod() {
System.out.println("outer a " + Outer.access$0(outer));
Outer.access$1(outer);
}
}
Outer I n n e r 类有个实例变量 o u t e r ,指向外部类的对象,在构造方法中被初始化, O u t e r 在新建 O u t e r Inner 类有个实例变量 outer,指向外部类的对象,在构造方法中被初始化,Outer 在新建 Outer Inner类有个实例变量outer,指向外部类的对象,在构造方法中被初始化,Outer在新建OuterInner 对象时给它传递当前 Outer 对象实例。
Outer$Inner 类还访问了外部类的私有变量和方法,Java 编译时自动为外部类 Outer 生成了两个非私有静态方法,access$0 用于访问变量 a,access$1 用于访问方法 action。
方法内部类 / 局部内部类
方法内部类定义在外部类的方法中,只能在定义的方法内使用。
如果方法是静态方法,则内部类只能访问外部类的静态成员变量和方法。如果方法是实例方法,则内部类可以访问外部类的静态和实例成员。
方法内部类还可以访问方法的参数和方法中的局部变量,不过,这些变量必须被声明为 final
或者名义上的常量(变量只赋值一次)。
调用方法时,局部变量如果没有用 final 修饰,它的生命周期就和方法的生命周期一样,在方法调用时入栈,调用结束时弹栈,局部变量将从内存中消失,如果方法内部类还没有消失,想使用该局部变量时,显然已经无法使用。如果使用 final 修饰,就会在类加载的时进入常量池中,不随方法弹栈而消失。
在 Java8 之前,必须显示使用 final 修饰,Java8 后,编译器会处理只赋值一次的变量为常量。
与普通的成员内部类相似,方法内部类也有一个实例变量指向外部类对象,在构造方法中被初始化,对外部类私有实例成员的访问也是通过编译时为外部类生成的静态访问方法。
方法内部类可以访问方法中的参数和局部变量,这是通过在构造方法中传递参数来实现的。
public class Outer {
private int a = 100;
public void test(final int param){
final String str = "hello";
class Inner {
public void innerMethod(){
System.out.println("outer a == " + a);
System.out.println("param == " + param);
System.out.println("str == " + str);
}
}
Inner inner = new Inner();
inner.innerMethod();
}
}
编译后代码大概如下:
public class Outer {
private int a = 100;
public void test(final int param){
final String str = "hello";
Outer$1Inner inner = new Outer$1Inner(this, param);
inner.innerMethod();
}
static int access$0(Outer outer){
return outer.a;
}
}
public class Outer$1Inner {
Outer outer;
int param;
Outer$1Inner(Outer outer, int param) {
this.outer = outer;
this.param = param;
}
public void innerMethod() {
System.out.println("outer a == " + Outer.access$0(outer));
System.out.println("param == " + param);
System.out.println("str == " + "hello");
}
}
str 没有作为参数传递,因为它被定义为了常量,直接使用即可。
匿名内部类
匿名内部类没有单独的类定义,常做参数使用,在创建对象的同时定义类。
new 父类(参数列表){
}
new 接口(){
}
匿名内部类只能被使用一次,用来创建一个对象。它没有名字,没有构造方法,但可以根据参数列表,调用对应的父类构造方法。
匿名内部类编译时也会生成一个独立的类,类的名字为外部类加数字编号,没有有意义的名字。
匿名内部类和方法内部类相似,匿名内部类能做的,方法内部类都能做。如果对象只会创建一次,且不需要构造方法来接受参数,则可以使用匿名内部类,这样更为简洁。
内部类的作用
无论外部类是否继承了某个类或实现了某个接口,内部类都能独立地继承类或实现接口,有效地实现了“多重继承”。
参考:《Java 编程的逻辑》 马俊昌