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. 多线程模式之Master-Worker

    一. 介绍 需要使用Master-Worker的场景:主线程开了多个子进程(Worker进程)去执行任务时,主线程希望能收集到每个子进程的执行结果. 所以,Master-Worker模式基本上就是: ...

  2. C# Excel文件导入操作

    Excel文件导出的操作我们经经常使用到,可是讲一个Excel文档导入并显示到界面还是第一次用到. 以下简介下在C#下怎样进行Excel文件的导入操作. 首先加入两个引用 using System.I ...

  3. Image与byte[]数组的相互转换

         近期项目有个需求是关于图片操作的,须要将图片保存到数据库中.经过尝试才知道Image类型文件是不能直接存储到数据库中的.保存之前须要我们做一步转换:将Image转换成字节数组类型Byte ...

  4. SWTBOK測试实践系列(1) -- 測试在项眼下期的评审投入划算吗?

    測试策略:静态測试还是动态測试? [对话场景] 成功公布某个软件版本号之后,项目团队召开了项目的经验教训总结大会.在会议期间,项目经理小项和測试经理小測进行了例如以下的对话: 小项:"小測, ...

  5. 设计模式之Build(生成者模式)

    一.生成器模式的定义: 生成器模式也称为建造者模式.生成器模式的意图在于将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示(GoF).在软件设计中,有时候面临着一个非常复杂的对象的创 ...

  6. 73,QT指针数组实战(指针数组与数组指针)

    //指针数组,每一个指针都是一个MainWindow // MainWindow *w[3][4]; // for(int i=0;i<3;i++) // { // for(int j=0;j& ...

  7. POJ 3050 枚举+dfs+set判重

    思路: 枚举+搜一下+判个重 ==AC //By SiriusRen #include <set> #include <cstdio> using namespace std; ...

  8. ElasticSearch vs 关系型数据库

    它们之间的关系,如下图所示.

  9. javafx tabPane

    public class EffectTest extends Application { @Override public void start(Stage primaryStage) { prim ...

  10. css相关用法

    1. 2. 3.offset([coordinates]) 获取匹配元素在当前视口的相对偏移. 返回的对象包含两个整型属性:top 和 left,以像素计.此方法只对可见元素有效. a.获取当前元素的 ...