IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 零基础java自学流程-Java语言进阶44 -> 正文阅读

[Java知识库]零基础java自学流程-Java语言进阶44

二.深入理解内部类

  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() {
             
        }
    }
}

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

?反编译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);

正如你所看到的,即使我们是内部类的构造函数定义为一个无参数的构造函数,默认情况下,编译器添加一个参数类型的对象的引用外部类的,所以从这个0指针成员内部类的指向外部类的对象。因此,可以在成员内部类中任意访问外部类的成员。如果不从外部类创建对象,就不能初始化Outter this&0引用并从内部类创建对象。

2.?为什么局部内部类和匿名内部类只能访问局部最终变量?

这个问题困扰了很多人,但是在讨论它之前,让我们看看下面的代码:

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。默认情况下,编译器会为匿名内部类和局部内部类起名为Outter1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。

  

?

根据上图,测试方法中的匿名内部类的名称叫做test $1。

在前面的代码中,如果a和b之前的任何一个变量被最终删除,则此代码将无法编译。让我们从下面的问题开始:

当测试方法执行时,变量A的生命期结束,但此时Thread对象的生命期还没有结束,因此无法在Thread run方法中继续访问变量A。Java使用复制来解决这个问题。反编译此代码的字节码会产生以下结果:

?

我们看到在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$1的构造函数接受两个参数:对外部类对象的引用和一个int。匿名内部类Test$1的构造函数接受两个参数:对外部类对象的引用和一个int。

也就是说,如果可以在编译时确定局部变量的值,则直接在匿名内部创建副本。如果在编译时无法确定局部变量的值,则通过向构造函数传递参数初始化副本。

从上面可以看到,在run方法中访问的变量A根本不是测试方法中变量A的本地变量。这解决了前面提到的生命周期不一致的问题。因为run方法中的变量A和测试方法中的变量A是不一样的,当变量A在run方法中被改变时会发生什么?

是的,这会导致数据不一致,不能满足最初的意图和需求。为了解决这个问题,Java编译器将变量A限制为final,并且不允许对变量A进行更改(对于引用类型的变量,不允许指向新对象),从而解决了数据不一致的问题。

在这一点上,应该清楚为什么方法中的局部变量和参数必须用final限定。

3.静态内部类有什么特殊之处吗?

正如您在上一节中看到的,静态内部类独立于外部类,这意味着无需创建外部类的对象,就可以创建内部类的对象。此外,静态内部类不保存对外部类对象的引用。尝试反编译类文件,看看是否有对Outter this&0的引用。


?想要系统学习JAVA推荐JAVA300集



Java300集零基础适合初学者视频教程

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-14 08:12:33  更:2021-10-14 08:12:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:15:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码