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. hdu 5312 Sequence(数学推导——三角形数)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5312 Sequence Time Limit: 2000/2000 MS (Java/Others)  ...

  2. java导入大量Excel时报错

    在项目中同事遇到一问题,如今给大家分享一下. 在程序里面导入两千多条数据后.程序就报错. 刚開始以为是内存的问题.在经过细致跟踪代码后发现每次都是833行的第三列报错.也就是第一万列.最后在网上找到了 ...

  3. android framework 02

    Android底层开发1.安装Ubuntu系统2.Ubuntu配置开发环境: sudo apt-get install git-core gnupg flex bison gperf zip sudo ...

  4. bootsrap中的偏移(栅格系统)

    在最初学习bootsrap这个框架的时候觉得这个框架中的栅格系统是个做自适应很好的工具,而且开发也很方便,是我接触的第一个前端框架,第一次觉得开发如此的简单,今天看到学妹写了一个后台的界面,虽然用到了 ...

  5. 码农Coding Peasant(s):一般指从事没有发展前景的软件开发职位

    码农Coding Peasant(s):一般指从事没有发展前景的软件开发职位,这种职位只能强化职业者在单方面的技术领域技能,学不到新技术,同时也是部分从事软件开发工作人员的一个自嘲的称号.一个依靠写代 ...

  6. SpringMVC-@RequestMapping的参数和用法

    RequestMapping里面的注解包含的参数如图: RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上.用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径. ...

  7. 从硬件到语言,详解C++的内存对齐(memory alignment)(一)

    作者:赵宗晟 出处:https://www.cnblogs.com/zhao-zongsheng/p/9099603.html 很多写C/C++的人都知道“内存对齐”的概念以及规则,但不一定对他有很深 ...

  8. Chrome不能在网易网盘中上传文件的解决办法

    Chrome不能在网易网盘中上传文件的解决办法1. 安装 Adobe Flash Player PPAPI,设置flash插件 chrome://settings/content/flash,许可[* ...

  9. 随时查看源码的网站---http://www.sooset.com/

    由于工作需要经常要在Windows平台下参阅linux源码,以前都用http://lxr.linux.no/来浏览源码(如下图所示),最近发现sooset来浏览更方便,所以介绍给大家分享. 650) ...

  10. <link rel="shortcut icon" href="Xubuntu.ico" type="image/x-icon" /> <LINK href="Xubuntu.ico" rel="shortcut icon"> <link href="Xubuntu.ico" rel="B

    <link rel="shortcut icon" href="Xubuntu.ico" type="image/x-icon" /& ...