# 深入理解Java中为什么内部类可以访问外部类的成员#(1)原创分享

# 深入理解Java中为什么内部类可以访问外部类的成员#

 ------影子侠开发者社区Rong原创分享

 ## 内部类简介

虽然Java是一门相对比较简单的编程语言,但是对于初学者, 还是有很多东西感觉云里雾里, 理解的不是很清晰。内部类就是一个经常让初学者感到迷惑的特性。 即使现在我自认为Java学的不错了, 但是依然不是很清楚。其中一个疑惑就是为什么内部类对象可以访问外部类对象中的成员(包括成员变量和成员方法)? 早就想对内部类这个特性一探究竟了,今天终于抽出时间把它研究了一下。

 

内部类就是定义在一个类内部的类。定义在类内部的类有两种情况:一种是被static关键字修饰的, 叫做静态内部类, 另一种是不被static关键字修饰的, 就是普通内部类。 在下文中所提到的内部类都是指这种不被static关键字修饰的普通内部类。 静态内部类虽然也定义在外部类的里面, 但是它只是在形式上(写法上)和外部类有关系, 其实在逻辑上和外部类并没有直接的关系。而一般的内部类,不仅在形式上和外部类有关系(写在外部类的里面), 在逻辑上也和外部类有联系。 这种逻辑上的关系可以总结为以下两点:

 

1 内部类对象的创建依赖于外部类对象;

 

2 内部类对象持有指向外部类对象的引用。

 

上边的第二条可以解释为什么在内部类中可以访问外部类的成员。就是因为内部类对象持有外部类对象的引用。但是我们不禁要问, 为什么会持有这个引用? 接着向下看, 答案在后面。

 

## 通过反编译字节码获得答案

 

在源代码层面, 我们无法看到原因,因为Java为了语法的简洁, 省略了很多该写的东西, 也就是说很多东西本来应该在源代码中写出, 但是为了方便起见, 不必在源码中写出,编译器在编译时会加上一些代码。 现在我们就看看Java的编译器为我们加上了什么?

 

首先建一个工程TestInnerClass用于测试。 在该工程中为了简单起见, 没有创建包, 所以源代码直接在默认包中。在该工程中, 只有下面一个简单的文件。

 

```java

 

public class Outer { 

    int outerField = 0; 

     

    class Inner{ 

        void InnerMethod(){ 

            int i = outerField; 

        } 

    } 

 

```

 

该文件很简单, 就不用过多介绍了。 在外部类Outer中定义了内部类Inner 并且在Inner的方法中访问了Outer的成员变量outerField

 

虽然这两个类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件:

 

Outer.class

 

Outer$Inner.class

 

 

这里我们的目的是探究内部类的行为, 所以只反编译内部类的class文件Outer$Inner.class 在命令行中, 切换到工程的bin目录, 输入以下命令反编译这个类文件:

 

```

javap -classpath . -v Outer$Inner

```

 

-classpath .   说明在当前目录下寻找要反编译的class文件

-v   加上这个参数输出的信息比较全面。包括常量池和方法内的局部变量表, 行号, 访问标志等等。

 

注意, 如果有包名的话, 要写class文件的全限定名, 如:

 

```

javap -classpath . -v com.baidu.Outer$Inner

```

 

反编译的输出结果很多, 为了篇幅考虑, 在这里我们省略了常量池。 下面给出除了常量池之外的输出信息。

 

```

  final Outer this$0; 

    flags: ACC_FINAL, ACC_SYNTHETIC 

 

 

  Outer$Inner(Outer); 

    flags: 

    Code: 

      stack=2, locals=2, args_size=2 

         0: aload_0 

         1: aload_1 

         2: putfield      #10                 // Field this$0:LOuter; 

         5: aload_0 

         6: invokespecial #12                 // Method java/lang/Object."<init>":()V 

         9: return 

      LineNumberTable: 

        line 5: 0 

      LocalVariableTable: 

        Start  Length  Slot  Name   Signature 

               0      10     0  this   LOuter$Inner; 

 

  void InnerMethod(); 

    flags: 

    Code: 

      stack=1, locals=2, args_size=1 

         0: aload_0 

         1: getfield      #10                 // Field this$0:LOuter; 

         4: getfield      #20                 // Field Outer.outerField:I 

         7: istore_1 

         8: return 

      LineNumberTable: 

        line 7: 0 

        line 8: 8 

      LocalVariableTable: 

        Start  Length  Slot  Name   Signature 

               0       9     0  this   LOuter$Inner; 

               8       1     1     i   I 

```

 

首先我们会看到, 第一行的信息如下:

 

```

final Outer this$0;

```

 

这句话的意思是, 在内部类Outer$Inner中, 存在一个名字为this$0 类型为Outer的成员变量, 并且这个变量是final的。 其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用”。但是我们在定义这个内部类的时候, 并没有声明它, 所以这个成员变量是编译器加上的。

 

虽然编译器在创建内部类时为它加上了一个指向外部类的引用, 但是这个引用是怎样赋值的呢?毕竟必须先给他赋值, 它才能指向外部类对象。 下面我们把注意力转移到构造函数上。 下面这段输出是关于构造函数的信息。

此文章系原创,如需转载,请注明出处影子侠开发者社区www.yingzixia.com


转载于:https://my.oschina.net/u/2312592/blog/373546

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值