写这篇文的起因是某个小孩(咳咳。。虽然本人技术上是小菜鸟,可也是位大妈。。)问了我两个问题:
在类A的方法a中定义一个内部类B,且B内也有一个方法b,
1、为什么内部类B的方法b,引用方法a里面的变量时,该变量前面要加final才能被引用?
2、如何从外部调用内部类B里的方法b?
下面是关于内部类的一些定义和理解:
1、内部类是指在一个类A里定义的另一个类B。
2、A为宿主类(Surrounding Class),B为A的内部类(Inner Class)。
3、如果内部类定义在A的某个方法里,则B也叫做A的本地内部类(Local Inner Class)。
4、如果内部类B的前面加了static修饰词,则称为嵌套类(Nested Class),嵌套类不能在宿主类的方法内定义,即嵌套类不可能是本地内部类(可以试试,编译器会报错)。嵌套类可以独自生成,但是一般的内部类生成实例需要借助宿主类的this引用。
5、内部类和嵌套类前面可以加表示权限的修饰词(public protected private),但是本地内部类不允许。
6、内部类和本地内部类的名字空间不冲突,可以重名(可以从它们编译生成的class文件名确认这一点)。因为内部类属于宿主类,而本地内部类只是属于宿主类的某个方法。
7、匿名内部类(Anonymous Inner Class),一般是在方法中调用默认构造函数生成。匿名内部类和本地内部类一样,引用外面的变量时,该变量前面必须有final修饰词。
8、内部类可以任意访问外部成员,但是反过来并不成立。即宿主类(外部类)的方法不能直接访问内部类的成员。
。。。
开始解题。(有的地方纯属猜测,如有错误,欢迎指正)
问题一:
(1)先解释为什么加了final的变量可以应用。
这个好说。因为根据java的定义,final变量不可更改,类似于常量,任何一个方法里引用常量都是没有限制的(当然final变量还要受权限限制,但问题1的前提是都在一个宿主类内,所以不存在这方面的考虑)。
(3)再解释为什么B的方法b可以任意引用类A的变量而没有其他限制。
因为类A里面的成员,无论属性成员还是方法成员,jvm保存它们的时候都会挂上一个其所属对象的引用,即A的引用。虽然B和A没有直接联系,B并不直接属于A,但是B属于A的方法a,a会挂有一个A的引用,运行时B的方法b可以通过a上挂的A引用找到A的成员变量。
(2)最后解释为什么方法a()里面的变量不可以使用。
个人以为,方法a里面的普通变量作为局部变量是没有挂this引用的,B没有办法找到它,如果引用它的话,相当于在一个类中使用一个未定义的变量,所以编译时就会报错。
问题二:
这个有点麻烦,B的作用域太局限了。最后我想的招是利用继承里面的方法复写,利用父类的引用-子类引用-子类复写的父类方法:A类也有一个方法成员b,本地内部类B继承外部类A,然后a方法返回A,调用的方式为new A().a().b()。
(《Thinking in Java》里其实有关于这种方式更详尽的讲解,还是第八章,在“回调”和“应用框架”这部分内容中)
下面是根据上面的内容整理的代码:
class A{
int i = 0;
void display(){
System.out.println("outter A: i = " + i);
}
}
class E{
// 这一句如果注释掉,main方法 里的test.run().eat()语句就会报错
void eat(){System.out.println("outter E");}
}
public class InnerTest{
int i = 12;
// 内部类,和外部的类A不冲突
class A{
String s = "A.s";
void display(){
System.out.println("in inner A: i = " + i);
}
}
// 嵌套类
// 从main方法可以看出,同是内部类,C可以独立创建实例,但是B却需要宿主类的实例(引用)
class B{} // static private Class B{}
static class C{
public C(){
System.out.println("static C in InnerTest");
}
}
public void testA(){
// 可以看出,生成的是内部类A实例;要生成外部的类A实例,必须在InnerTest类之外
A a = new A();
a.display();
}
// 这一句如果注释掉,main方法 里的test.run().eat()语句就会报错
void eat(){System.out.println("A");}; // 传说中的钩子??
InnerTest runC(){
int j = 7;
final int x = 99;
// 和runC方法外面的内部类不冲突
class C extends InnerTest{
int k = 13;
void test(){
System.out.println(i + x); // 可以调用InnerTest的普通变量i,但是调用变量j会报错
}
void eat(){
System.out.println("C in runC()");
}
}
return new C();
}
// 注意对比runC和runE方法的调用结果
E runE(){
class D extends E{
void eat(){
System.out.println("D in runE()");
}
}
return new E();
}
public void show(){
// System.out.println(s); // 报错!
System.out.println(new A().s);
}
public static void main(String[] args) {
InnerTest test = new InnerTest();
test.testA();
//B b = new B(); // 报错!
B b = test.new B();
C c = new C();
test.runC().eat(); //打印结果是调用的本地内部类C的eat()方法
test.runE().eat(); //打印结果是调用的父类E的eat()方法,而不是本地内部类D的eat()方法
}
}
关于内部类更详细的知识,可以翻阅张孝祥老师写的《java就业培训教程》(http://www.itcast.cn/books)第三章第八节-内部类或《Thinking in Java》第八章-接口与内部类(这两本结合着看更容易全面理解,因为两人对内部类的讲解方式和侧重点不一样,张老师是实战派,内容图文并茂,更形象更容易理解,而且有一些学习的好经验和小技巧,而Bruce Eckel是学院派--不过他自己也许不这么认为,内容更全),或者自行google百度,或者亲自来传智播客请教老师:)