1.为什么成员内部类能够无条件訪问外部类的成员?

  在此之前,我们已经讨论过了成员内部类能够无条件訪问外部类的成员,那详细到底是怎样实现的呢?以下通过反编译字节码文件看看到底。其实,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件。以下是Outter.java的代码:


public class Outter {
private Inner inner = null;
public Outter() { } public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
} protected class Inner {
public Inner() { }
}
}

  编译之后,出现了两个字节码文件:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

  反编译Outter$Inner.class文件得到以下信息:


E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.Object
SourceFile: "Outter.java"
InnerClass:
#24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
t2/Outter
minor version: 0
major version: 50
Constant pool:
const #1 = class #2; // com/cxh/test2/Outter$Inner
const #2 = Asciz com/cxh/test2/Outter$Inner;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz this$0;
const #6 = Asciz Lcom/cxh/test2/Outter;;
const #7 = Asciz <init>;
const #8 = Asciz (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz Code;
const #10 = Field #1.#11; // com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
est2/Outter;
const #11 = NameAndType #5:#6;// this$0:Lcom/cxh/test2/Outter;
const #12 = Method #3.#13; // java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;// "<init>":()V
const #14 = Asciz ()V;
const #15 = Asciz LineNumberTable;
const #16 = Asciz LocalVariableTable;
const #17 = Asciz this;
const #18 = Asciz Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz SourceFile;
const #20 = Asciz Outter.java;
const #21 = Asciz InnerClasses;
const #22 = class #23; // com/cxh/test2/Outter
const #23 = Asciz com/cxh/test2/Outter;
const #24 = Asciz Inner; {
final com.cxh.test2.Outter this$0; public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
Code:
Stack=2, Locals=2, Args_size=2
0: aload_0
1: aload_1
2: putfield #10; //Field this$0:Lcom/cxh/test2/Outter;
5: aload_0
6: invokespecial #12; //Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 16: 0
line 18: 9 LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/cxh/test2/Outter$Inner; }

  第11行到35行是常量池的内容。以下逐一第38行的内容:

final com.cxh.test2.Outter this$0;

 

 这行是一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默觉得成员内部类加入了一个指向外部类对象的引用,那么这个引用是怎样赋初值的呢?以下接着看内部类的构造器:

public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

  从这里能够看出,尽管我们在定义的内部类的构造器是无參构造器,编译器还是会默认加入一个參数,该參数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此能够在成员内部类中任意訪问外部类的成员。

从这里也间接说明了成员内部类是依赖于外部类的。假设没有创建外部类的对象,则无法对Outter
this&0引用进行初始化赋值。也就无法创建成员内部类的对象了。所以,假设在外部类没有人引用的时候。而成员内部类有人引用,外部类由于被内部类引用所以不会被回收。这就是Android中常见的Activity内存泄露产生的原因。



2.为什么局部内部类和匿名内部类仅仅能訪问局部final变量?

  想必这个问题也以前困扰过非常多人,在讨论这个问题之前。先看以下这段代码:

public class Test {
public static void main(String[] args) { } public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
System.out.println(b);
};
}.start();
}
}

  这段代码会被编译成两个class文件:Test.class和Test1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。

  

  依据上图可知,test方法中的匿名内部类的名字被起为 Test$1。

  上段代码中。假设把变量a和b前面的任一个final去掉。这段代码都编译只是。我们先考虑这样一个问题:

  当test方法运行完成之后,变量a的生命周期就结束了,而此时Thread对象的生命周期非常可能还没有结束,那么在Thread的run方法中继续訪问变量a就变成不可能了。可是又要实现这种效果,怎么办呢?Java採用了 复制  的手段来解决问题。将这段代码的字节码反编译能够得到以下的内容:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

我们看到在run方法中有一条指令:

bipush 10

这条指令表示将操作数10压栈。表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,假设这个变量的值在编译期间能够确定。则编译器默认会在匿名内部类(局部内部类)的常量池中加入一个内容相等的字面量或直接将对应的字节码嵌入到运行字节码中。

这样一来,匿名内部类使用的变量是还有一个局部变量,仅仅只是值和方法中局部变量的值相等,因此和方法中的局部变量全然独立开。

  以下再看一个样例:

public class Test {
public static void main(String[] args) { } public void test(final int a) {
new Thread(){
public void run() {
System.out.println(a);
};
}.start();
}
}

反编译得到:

  我们看到匿名内部类Test$1的构造器含有两个參数。一个是指向外部类对象的引用。一个是int型变量,非常显然,这里是将变量test方法中的形參a以參数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

  也就说假设局部变量的值在编译期间就能够确定。则直接在匿名内部里面创建一个拷贝。假设局部变量的值无法在编译期间确定。则通过构造器传參的方式来对拷贝进行初始化赋值。

  从上面能够看出,在run方法中訪问的变量a根本就不是test方法中的局部变量a。

这样一来就攻克了前面所说的 生命周期不一致的问题。可是新的问题又来了,既然在run方法中訪问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

  对,会造成数据不一致性,这样就达不到原本的意图和要求。

为了解决问题。java编译器就限定必须将变量a限制为final变量。不同意对变量a进行更改(对于引用类型的变量,是不同意指向新的对象),这样数据不一致性的问题就得以攻克了。

  到这里,想必大家应该清楚为何 方法中的局部变量和形參都必须用final进行限定了。

  3.静态内部类有特殊的地方吗?


  从前面能够知道,静态内部类是不依赖于外部类的,也就说能够在不创建外部类对象的情况下创建内部类的对象。

另外,静态内部类是不持有指向外部类对象的引用的,这个读者能够自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。

从反编译深入理解JAVA内部类类结构以及finalkeyword的更多相关文章

  1. 通过反编译深入理解Java String及intern(转)

    通过反编译深入理解Java String及intern 原文传送门:http://www.cnblogs.com/paddix/p/5326863.html 一.字符串问题 字符串在我们平时的编码工作 ...

  2. 通过反编译深入理解Java String及intern

    一.字符串问题 字符串在我们平时的编码工作中其实用的非常多,并且用起来也比较简单,所以很少有人对其做特别深入的研究.倒是面试或者笔试的时候,往往会涉及比较深入和难度大一点的问题.我在招聘的时候也偶尔会 ...

  3. (转)通过反编译深入理解Java String及intern

    原文链接:https://www.cnblogs.com/paddix/p/5326863.html 一.字符串问题 字符串在我们平时的编码工作中用的非常多,并且用起来非常简单,所以很少有人对其做特别 ...

  4. 夯实Java基础系列18:深入理解Java内部类及其实现原理

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  5. Java 干货之深入理解Java内部类

    可以将一个类定义在另一个类或方法中,这样的类叫做内部类 --<Thinking in Java> 说起内部类,大家并不陌生,并且会经常在实例化容器的时候使用到它.但是内部类的具体细节语法, ...

  6. 深入理解Java内部类

         内部类就是定义在一个类中的另外一个类,是一种从属关系.在没有实际了解内部类之前,我始终困惑,为什么要在一个类中定义另外一个类,这不是增加代码结构复杂度么?现在才大致能知道这种设计的优势是大于 ...

  7. 反编译Apk得到Java源代码

    原文章转载自:http://hi.baidu.com/%CB%BF%D4%B5%CC%EC%CF%C2/blog/item/2284e2debafc541e495403ec.html 本人转载自:ht ...

  8. Java反编译工具:Java Decompiler

    Java Decompiler项目旨在开发一套工具集,这套工具集可以反编译并分析Java5之后的Java字节码. 它主要包括四个部分. JD-Core:Java Decompiler的核心库,它能够根 ...

  9. java7(1)——反编译深入理解增强的switch(读字节命令实战)

    [本文介绍] 本文主要讲java_7 的改进switch的底层实现.反编译一个使用带String的switch的demo并一步步解析反编译出来的字节命令,从编译的角度解读switch的底层实现. [正 ...

随机推荐

  1. 线段树 hdu3642 Get The Treasury

    不得不说,这是一题很经典的体积并.. 然而还是debug了2个多小时... 首先思路:按z的大小排序. 然后相当于扫描面一样,,从体积的最下方向上方扫描,遇到这个面 就将相应的两条线增加到set中,或 ...

  2. OcadeToolkit - From 2D CAD to PDMS

    OcadeToolkit - From 2D CAD to PDMS eryar@163.com Abstract. 基于开源二维CAD软件QCAD开发的插件可以将DXF文件中直线.圆弧转换到PDMS ...

  3. Java图像渐变

    图像渐变我们大体想一下思路无非是这样:将图像所有的像素点的RBG,每个点就减去相同的量,而且这个量是个渐变的量.是的,就是这样,我们的程序也是这个思路,不过就是没有单纯的“想”这么简单了.我这里只编写 ...

  4. Can't access RabbitMQ web management interface after fresh install

    http://stackoverflow.com/questions/22850546/cant-access-rabbitmq-web-management-interface-after-fres ...

  5. javafx mouseEvent

    public class EffectTest extends Application { Path path; @Override public void start(Stage primarySt ...

  6. Autoencoders and Sparsity(二)

    In this problem set, you will implement the sparse autoencoder algorithm, and show how it discovers ...

  7. STANDBY REDO LOG

    SRL Introduce 从">ORACLE9i开始,出现了Standby Redo Logs(SRL),9.1开始只有">physical standby支持SRL ...

  8. 小程序中关于获取app实例与当前组件

    1.getApp()来获取 App 实例 2.getCurrentPages()获取前页面栈

  9. 【Pycharm】【HTML/jQuery】代码换行问题

    问题:从网上下载jQuery文件后发现代码太长,不利于阅读:Pycharm没有预先设置好js文件的自动换行设置 问题 解决办法 解决后

  10. Android app : use html or native?

    Android app可分为两种:网络(html)应用程序和原生(native)应用程序 首先,我们先来讨论下如何判断一个app是html实现还是native实现. 设置-->>开发者选项 ...