在 Java 语言中,许多语言层面的细节是无法直接看到的。比如枚举的实现、泛型的擦除、自动拆箱装箱以及本次将说明的内部类的实现。主要原因在于编译器在编译我们的源代码时做了许多的改动,这时需要借助反编译的功能查看都做了哪些改动。在线反编译网站
内部类的种类
非静态
1. 非匿名类
public class InnerClassType {
class InnerClass {}
}
2. 匿名类
public class InnerClassType {
Object obj = new Object() {
@Override
public String toString() {
return "Anonymous inner class";
}
};
}
静态
1. 非匿名类
public class InnerClassType {
static class StaticInnerClass {}
}
2. 匿名类
public static Object anonymousStaticInnerClass = new Object() {
@Override
public String toString() {
return "Anonymous static inner class";
}
};
局部
1. 非匿名类
public void local() {
class LocalInnerClass {}
}
2. 匿名类
public void local() {
Object anonymousLocalInnerClass = new Object() {
@Override
public String toString() {
return "Anonymous local inner class";
}
};
}
总结下来其实就是作用域与是否为匿名
的组合。下面会以局部匿名类为例,其余的情况根据自己写的 Demo 代码通过上面的反编译网站反编译后就可以看到是如何实现的了。
局部匿名类案例
源代码:
public class Printer {
private int count;
public void print() {
}
public Printer create(final String msg) {
return new Printer() {
@Override
public void print() {
System.out.println(++count + ": " + msg);
}
};
}
}
将 Class 文件反编译后:
public class Printer {
private int count;
public void print() {
}
public Printer create(final String msg) {
return new Printer$1(this, msg);
}
// $FF: synthetic method
static int access$004(Printer x0) {
return ++x0.count;
}
}
class Printer$1 extends Printer {
// $FF: synthetic field
final String val$msg;
// $FF: synthetic field
final Printer this$0;
Printer$1(final Printer this$0, final String val$msg) {
this.this$0 = this$0;
this.val$msg = val$msg;
}
public void print() {
System.out.println(Printer.access$004(this.this$0) + ": " + this.val$msg);
}
}
会发现,编译器会帮我们生成一个继承自源类
的局部匿名类的实现类 Preinter$1
。来看一下它的构造方法:
Printer$1(final Printer this$0, final String val$msg) {
this.this$0 = this$0;
this.val$msg = val$msg;
}
this$0
为外部类的引用val$msg
为 create 方法的形参msg
实现很简单一目了然。
- 为什么内部类中可以直接访问外部类的数据?就是因为内部类的内部维护了一个外部类的引用。
- 为什么内部类可以使用外部类的非成员数据?比如方法的局部变量,也是因为内部类通过构造器将局部变量传递到了内部,维护成了一个
成员常量
。 并且外部类方法的局部变量也必须通过 final 修饰,避免外部方法对变量的修改影响了内部类中维护的相同变量
。也就是,当使用了内部类,此时外部类的环境就不允许变更了,但成员数据除外
。为什么下面会说到。
访问外部类的成员、局部数据间的差异
访问外部类的成员变量与访问外部类的局部变量是不一样的。可以观察一下生成在外部类中的方法 access$004
与 该方法的调用:
static int access$004(Printer x0) {
return ++x0.count;
}
public void print() {
System.out.println(Printer.access$004(this.this$0) + ": " + this.val$msg);
}
最终通过外部类的引用访问外部类的成员变量。思考一下,为什么不像处理局部变量那样处理成员变量呢?不能因为内部类的应用,而强迫使可能会长期驻留主存的、线程共享可读可写的成员数据都具有常量性质
。