`
你的世界
  • 浏览: 1998 次
文章分类
社区版块
存档分类
最新评论

Java内部类final语义实现

 
阅读更多
本文描述在java内部类中,经常会引用外部类的变量信息。但是这些变量信息是如何传递给内部类的,在表面上并没有相应的线索。本文从字节码层描述在内部类中是如何实现这些语义的。

本地临时变量 基本类型

final int x = 10; new Runnable() { @Override public void run() {
        System.out.println(x);
    }
}.run();
当输出内部类字节码(javap -p -s -c -v)时,如下所示:

0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream; 3: bipush        10 5: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V 8: return
可以看出,此常量值直接被写在内部类的临时变量中,即相当于进行了一次变量copy。

本地临时变量 引用类型

final T t = new T(); new Runnable() { @Override public void run() {
        System.out.println(t);
    }
}.run();
字节码变为如下所示:

final T val$t;
    flags: ACC_FINAL, ACC_SYNTHETIC

  T$1(T);
    Signature: (LT;)V //构建函数的字节码 0: aload_0 1: aload_1 2: putfield #1 // Field val$t:LT; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return //main函数字节码 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: getfield #1 // Field val$t:LT; 7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 10: return
可以看出,这时自动生成了一个带有1个参数的构造函数,并且将相应的t值作为参数传递到内部类当中,同时设定final语义,即不能被内部类修改。

上面的是无参构造函数,如果是一个有参数的内部类呢,如下所示:

Thread thread = new Thread("thread-1") {
@Override public void run() {
System.out.println(t);
}
};
生成的字节码如下:

T$1(java.lang.String, T); Signature: (Ljava/lang/String;LT;)V
可以看出,编译器将自动对原来调用的构造函数进行了修改,将原来只需要1个参数的构造函数 修改为传2个参数,并且同时将相应的t传递进去。

引用字段,基本类型

int t = 3; private void xx() { new Runnable() {
        @Override public void run() {
            System.out.println(t);
        }
    }.run();
}
生成的字节码如下:

T$1(T);
  Signature: (LT;)V
  flags:

  Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:LT; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return
这里并没有如临时变量那样,直接在内部类中进行常量定义。为什么?因为这里的t对象随时可能被修改。

引用字段,引用类型

final String t = new String("abc"); private void xx() { new Runnable() { @Override public void run() {
            System.out.println(t);
        }
    }.run();
}
生成字节码如下:

final T this$0;
    Signature: LT;

  T$1(T); //内部类构造函数 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:LT; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return
这里,在内部类的构造函数中,直接将外部类的this传递进来了,因此在内部类的run方法中,对于t,将直接两层getField进行调用,即可以拿到相应的信息。如下所示:

0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0  4: getfield      #1                  // Field this$0:LT; 7: getfield      #4                  // Field T.t:Ljava/lang/String; 10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13: return
引用类型,引用类型,static字段

static String t = new String("abc"); private void xx() { new Runnable() {
@Override public void run() {
System.out.println(t);
}
}.run();
}
字节码如下:

final T this$0;
    Signature: LT;
    flags: ACC_FINAL, ACC_SYNTHETIC

  T$1(T);
    Signature: (LT;)V //构造函数字节码 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:LT; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return //run方法字节码 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #4 // Field T.t:Ljava/lang/String; 6: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 9: return
可以看出,即使是引用static字段,在内部类中仍然会保留外部类的引用,即达到引用目的。同时,在run方法内部,因为是static字段,因此将不再使用getField,而是使用getStatic来进行相应字段的引用。

总结

在整个内部类字节码的生成规则中,主要采用了修改构造函数的方式来将需要在整个内部类中引用的变量进行参数传递。如果你真的想学习java你可以来这个群前面是五二七,中间是四一三后面是一四四,这里有技术大牛亲自指导帮助你 还有免费的直播课程学习,并且,因为是内部类,构造函数是已知的,可以随意的修改。针对特定的场景,可以进行一定的优化,如常量化(临时变量基本类型)。

因为在整个JVM层,并没有针对内部类作特殊的处理,因此这些处理手法都是在编译层进行处理的。同时,在语言层,针对这些生成的信息进行指定的说明。如SYNTHETIC语义。

在反射字段Member层,定义了如下方法:

/**
* Returns {@code true} if this member was introduced by
* the compiler; returns {@code false} otherwise.
*
* @return true if and only if this member was introduced by
* the compiler.
* @jls 13.1 The Form of a Binary
* @since 1.5
*/ public boolean isSynthetic();
即此信息是由编译器引入的。

了解这些对于整个语言层有一定的理解意义,但并不代表将来这些不会会改变,了解一些实现细节有助于自己在代码实现层有进一步的思考空间,并不局限于之前所了解的信息。
分享到:
评论

相关推荐

    java内部学习笔记.docx

    1. 编程语言:编程语言是用于编写计算机程序的正式语言,它是一种规范化的语法和语义规则集合,Java是其中的一种高级编程语言。 2. Java的特点:Java具有平台独立性(Write Once, Run Anywhere,简称WORA)、面向...

    Java面向对象面试题.pdf

    13. 用 JAVA 实现一种排序,JAVA 类实现序列化的方法(二种)。可以使用快速排序或插入排序等方法来实现排序,并使用 Serializable 接口来实现序列化。 14. 在 COLLECTION 框架中,实现比较要实现什么样的接口?...

    The Java Language Specification, Java SE 6 Edition.rar

    8. 内部类:Java 6支持内部类,包括成员内部类、局部内部类和匿名内部类。它们可以提供更灵活的代码组织和封装,尤其是在处理事件驱动编程和回调时。 9. 泛型:Java 5引入泛型,Java 6继续改进。泛型增强了类型安全...

    JAVA面试大全

    9. **匿名内部类**:匿名内部类没有名称,可以直接实现接口或继承类,但每个匿名内部类只能实现一个接口或继承一个类。 10. **静态嵌套类(Static Nested Class)与内部类(Inner Class)**:内部类是嵌套在另一个...

    经典Java面试题.pdf

    不能 extends 其它类,但一个内部类可以作为一个接口,由另一个内部类实现。 Static Nested Class 和 Inner Class 的不同:Nested Class 一般是 C++ 的说法,Inner Class 是 Java 的说法。Static Nested Class 是...

    Java程序员经典面试题37道及答案

    9. **匿名内部类**:匿名内部类不能继承其他类,但可以实现接口。它们通常用于简洁地定义回调或事件监听器。 10. **静态嵌套类(Static Nested Class)与内部类(Inner Class)**:静态嵌套类像普通类一样,可以...

    Java虚拟机规范(Java SE 7)1

    它涉及到volatile变量、synchronized关键字、final字段的语义以及线程交互的内存操作。 7. **异常处理**:Java虚拟机支持异常处理框架,通过try-catch-finally结构捕获和处理异常。异常处理表在类的常量池中维护,...

    java期末考试试题含复习资料

    Java 语言中有 53 个关键字,包括 public、private、protected、static、final、abstract 等。这些关键字用于定义类、变量、方法等,具有特殊的含义和用途。 2. Java 代码执行过程 Java 代码执行过程包括编译、...

    Java虚拟机规范(Java_SE_7).pdf

    这涉及到 volatile、synchronized、final 关键字的语义以及 Happens-Before 规则。 7. **异常处理**:JVM支持异常处理框架,通过try-catch-finally语句块来捕获和处理异常,提供了一种统一的错误处理机制。 8. **...

    2014java面试题

    本文对 Java 面试题进行详细的解析,涵盖了 Java 语言的异常处理机制、接口和虚类、垃圾回收机制、线程同步、析构函数和虚函数、Error 与 Exception 的区别、final 类型、编程风格、堆和栈的区别、超大整数的存储和...

    解析Java虚拟机中类的初始化及加载器的父委托机制共14页

    在实际开发中,自定义类加载器或者理解类加载过程可以帮助我们解决特定问题,比如实现类的热替换、隔离不同版本的类库等。然而,如果不熟悉父委托机制,可能会导致类加载混乱,产生如类定义冲突等问题。 总结,理解...

    深入java虚拟机笔记

    这种层次化的结构有助于实现类隔离以及安全性控制。 - **Java Class文件**:Java源代码经过编译后生成的字节码文件,包含了类的定义、方法、字段等信息。这些信息是JVM执行Java程序的基础。 - **Java API**:Java ...

    JAVA考试题--语言类考试面试必备2

    - Java中类只能实现单重继承。 - 接口可以实现多重继承。 **17. Java线程的实现方式** - **继承Thread类**:创建Thread子类并重写run()方法。 - **实现Runnable接口**:实现run()方法并通过Thread类来启动。 **18...

    JAVA题库-JAVA面试题目整理.doc

    然而,匿名内部类可以继承抽象类,前提是抽象类也是匿名的,即一个匿名内部类可以实现接口的同时继承另一个匿名抽象类。 以上是对JAVA面试中常见的一些知识点的详细阐述,涵盖了面向对象、异常处理、多线程、内存...

    java异常1.pdf

    匿名内部类可以在不定义名称的情况下创建类的实例,它可以实现接口但不能直接继承其他类。静态嵌套类(Static Nested Class)与普通内部类(Inner Class)的区别在于,静态嵌套类不持有对外部类的引用,可以直接被...

    java笔试中的基础知识

    集合框架是Java中数据结构的重要组成部分,`Collection`、`Collections`和`Map`接口及其实现类(如`HashMap`、`Hashtable`)提供了存储和操作数据的灵活方式。`HashMap`允许键和值为`null`,而`Hashtable`不允许任何...

Global site tag (gtag.js) - Google Analytics