开宗明义,本文主要阐述java内部类。
关于内部类的种类,网文中有很多介绍,大概分成了四类:
1 成员内部类。
2 静态内部类
3 局部内部类
4 匿名内部类
其中1和2是定义在类内部的类,2有static限定词修饰,被称为静态内部类。
3和4是定义在方法内部的类
一、成员内部类
首先要说明一点,所谓的内部类都是编译期间的概念。在编译完成生成之后,对于虚拟机而言,内部类和其他的类没有任何区别。
内部类会在外部类的同包目录下生成一个outerclassname$innerclassnmae.class的class文件。
import java.lang.reflect.*;
public class Outer1 {
public Inner in = new Inner();
public other oth = new other();
private int data = 0;
class Inner{
private int in_data;
void print(){
// the inner can access the private field of outer class directly
System.out.println(data);
System.out.println(in.in_data);
}
}
private void OutMethod(){
// the outer class can't access the private field of inner class
// System.out.println(in_data);
// but the private filed of inner class is visible of outer class
System.out.println(in.in_data);
// in_data cannot be resolved or is not a field
// System.out.println(oth.in_data);
}
public static void main(String[] args){
try {
Class c = Class.forName("Outer1");
Method m[] = c.getDeclaredMethods();
System.out.println("***all methods of Outer1:");
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
Field[] f = c.getDeclaredFields();
System.out.println("\n***all fields of Outer1:");
for (int i = 0; i < f.length; i++)
System.out.println(f[i].toString());
Class o = Class.forName("Outer1$Inner");
Method om[] = o.getDeclaredMethods();
System.out.println("\n\n***all methods of Outer1$Inner:");
for (int i = 0; i < om.length; i++)
System.out.println(om[i].toString());
Field[] of = o.getDeclaredFields();
System.out.println("\n***all fields of Outer1$Inner:");
for (int i = 0; i < of.length; i++)
System.out.println(of[i].toString());
}catch (Throwable e){
System.err.println(e);
}
}
}
class other{
private int data;
Outer1 our1 = new Outer1();
Outer1.Inner o1in = our1.new Inner();
}
既然内部类在编译之后跟外部类没有什么区别,但是它确又具有内部类的特性:可以直接访问外部类的所有成员,包括私有的,那其中必有蹊跷.
为了找出内部的原因,我利用反射原理,将内部类和外部内的所有field和method打印出来:
***all methods of Outer1:
public static void Outer1.main(java.lang.String[])
private void Outer1.OutMethod()
static int Outer1.access$0(Outer1)
***all fields of Outer1:
public Outer1$Inner Outer1.in
public other Outer1.oth
private int Outer1.data
***all methods of Outer1$Inner:
void Outer1$Inner.print()
static int Outer1$Inner.access$0(Outer1$Inner)
***all fields of Outer1$Inner:
private int Outer1$Inner.in_data
final Outer1 Outer1$Inner.this$0
大家可以注意到,
在外部类中:
static int Outer1.access$0(Outer1)
在内部类中:
final Outer1 Outer1$Inner.this$0
我们可以发现,内部类中有一个外部类的引用,这个可以解释为什么在创建内部类的时候,其外部类对象必须已经构建.有了这个外部类的reference之后,我们可以无障碍的访问外部类的非私有变量了(我们已经知道,内部类和外部类是处于同一个包的)
为了进一步让内部类无障碍的访问外部类的私有域,外部类提供了方法static int Outer1.access$0(Outer1),编译器会在内部类访问外部类的时候,将System.out.println(data)替换成System.out.println(this$0.access$0(Outer1)),这样就达到了目的.
那么有人就会想那外部类是否可以直接访问内部类的成员呢? 这个其实不是一个问题, 当然是不行的,因为外部类是可以单处存在的,在没有构建内部类的时候,你访问什么呢?
外部类对内部类的访问和对非内部类的访问权限也是不一样的,从例子中可以看出来,外部类可以直接通过内部类的实例对象访问内部类的私有域,而非内部类的私有域则对同包中的其他类是不可见的.这又是为什么呢.
有了上面的经验,我们很容易发现,在内部类中,也有一个方法:
static int Outer1$Inner.access$0(Outer1$Inner)
外部类就是通过这个方法来访问内部类的所有域的.
二、静态内部类
如果在内部类的加上限定符,事情就发生了改变,这个类就成了静态内部类。
import java.lang.reflect.*;
public class Outer2 {
public Inner in = new Inner();
public other oth = new other();
private int data = 0;
static class Inner{
private int in_data;
void print(){
// the inner can access the private field of outer class directly
System.out.println(data);
System.out.println(in.in_data);
}
}
private void OutMethod(){
// the outer class can't access the private field of inner class
// System.out.println(in_data);
// but the private filed of inner class is visible of outer class
System.out.println(in.in_data);
// in_data cannot be resolved or is not a field
// System.out.println(oth.in_data);
}
public static void main(String[] args){
try {
Class c = Class.forName("Outer2");
Method m[] = c.getDeclaredMethods();
System.out.println("***all methods of Outer2:");
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
Field[] f = c.getDeclaredFields();
System.out.println("\n***all fields of Outer2:");
for (int i = 0; i < f.length; i++)
System.out.println(f[i].toString());
Class o = Class.forName("Outer2$Inner");
Method om[] = o.getDeclaredMethods();
System.out.println("\n\n***all methods of Outer2$Inner:");
for (int i = 0; i < om.length; i++)
System.out.println(om[i].toString());
Field[] of = o.getDeclaredFields();
System.out.println("\n***all fields of Outer2$Inner:");
for (int i = 0; i < of.length; i++)
System.out.println(of[i].toString());
}catch (Throwable e){
System.err.println(e);
}
}
}
class other2{
private int data;
Outer2 our2 = new Outer2();
Outer2.Inner o2in = our2.new Inner();
}
先贴代码,在原有的基础上只加上了一个限定词"static"。这样有三个地方出现了编译错误 :
1 System.out.println(data);
2 System.out.println(in.in_data);
3 Outer2.Inner o2in = our2.new Inner();
编译器给的提示分别是:
1 Cannot make a static reference to the non-static field data
2 Cannot make a static reference to the non-static field data
3 Illegal enclosing instance specification for type Outer2.Inner
将三处编译错误注释掉之后,输出如下:
***all methods of Outer2:
public static void Outer2.main(java.lang.String[])
private void Outer2.OutMethod()
***all fields of Outer2:
public Outer2$Inner Outer2.in
public other Outer2.oth
private int Outer2.data
***all methods of Outer2$Inner:
void Outer2$Inner.print()
static int Outer2$Inner.access$0(Outer2$Inner)
***all fields of Outer2$Inner:
private int Outer2$Inner.in_data
我们发现内部类已经没有了指向外部类的引用。这样,内部类就失去了直接访问外部类非静态域的能力。同时注意外部类也没有提供access方法了。
但是内部类的access方法还存在,参数为内部类的实例类型,这表明外部类还是可以通过内部类的实例对象直接访问内部类的所有域(包括私有域),这一点从
System.out.println(in.in_data);
可以看出来。
接下来我们顺着编译器的提示,将外部类的data域改成static的,则System.out.println(data);可以通过编译。
执行结果如下:
***all methods of Outer2:
public static void Outer2.main(java.lang.String[])
private void Outer2.OutMethod()
static int Outer2.access$0()
***all fields of Outer2:
public Outer2$Inner Outer2.in
public other Outer2.oth
private static int Outer2.data
***all methods of Outer2$Inner:
void Outer2$Inner.print()
static int Outer2$Inner.access$0(Outer2$Inner)
***all fields of Outer2$Inner:
private int Outer2$Inner.in_data
其中的多了如下一项:
static int Outer2.access$0()
这个是不是似曾相识?但是这个方法是没有参数的,没有了外部类的实例对象作参数,失去了访问非静态方法的能力,但是访问静态域还是没有问题的。
另外,在上面我们已经提到过,静态内部类已经没有了指向外部类的引用,这样,其实例化方法也发生了改变,不再依赖其外部类的实例化对象,可以通过如下方式直接实例化:
Outer2.Inner o2in1 = new Outer2.Inner();
已经有点长了,先写这么多。下次继续分析一下局部内部类和匿名内部类。
水平有限,写得不免粗糙,欢迎大家一起讨论。