传智播客-内部类

写这篇文的起因是某个小孩(咳咳。。虽然本人技术上是小菜鸟,可也是位大妈。。)问了我两个问题:
在类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百度,或者亲自来传智播客请教老师:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值