本文描述在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来进行相应字段的引用。

总结

在整个内部类字节码的生成规则中,主要采用了修改构造函数的方式来将需要在整个内部类中引用的变量进行参数传递。并且,因为是内部类,构造函数是已知的,可以随意的修改。针对特定的场景,可以进行一定的优化,如常量化(临时变量基本类型)。

因为在整个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的同学注意了!!! 
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:454297367 我们一起学Java!

Java内部类final语义实现的更多相关文章

  1. 为什么java内部类访问局部变量必须声明为final?

    https://blog.csdn.net/z55887/article/details/49229491 先抛出让我疑惑了很久的一个问题 编程时,在线程中使用局部变量时候经常编译器会提示:局部变量必 ...

  2. Java内部类详解

    Java内部类详解 说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就 ...

  3. 黑马----JAVA内部类

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 黑马程序员--JAVA内部类 一.内部类分为显式内部类和匿名内部类. 二.显式内部类 1.即显式声明的内部类,它有类名. 2.显 ...

  4. java 内部类 *** 最爱那水货

    注: 转载于http://blog.csdn.net/jiangxinyu/article/details/8177326 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类.内部类又 ...

  5. java内部类和匿名内部类

    内部类即是包含在类里面的又一个类. java内部类分为: 成员内部类.静态嵌套类.方法内部类.匿名内部类 . 内部类的共性 (1).内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.clas ...

  6. Java内部类小程序(成员内部类,静态内部类,匿名内部类)

    /** * 测试java内部类(成员内部类,静态内部类,匿名内部类) * 局部内部类不常用,就不写了. * @package :java05 * @author shaobn * @Describe ...

  7. [转] Java内部类详解

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  8. java内部类的作用分析

    提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比.内部类从表面上看,就 ...

  9. JAVA内部类(转)

    源出处:JAVA内部类 在java语言中,有一种类叫做内部类(inner class),也称为嵌入类(nested class),它是定义在其他类的内部.内部类作为其外部类的一个成员,与其他成员一样, ...

随机推荐

  1. maven 快照

    大型应用软件一般由多个模块组成,一般它是多个团队开发同一个应用程序的不同模块,这是比较常见的场景.例如,一个团队正在对应用程序的应用程序,用户界面项目(app-ui.jar:1.0) 的前端进行开发, ...

  2. 希尔排序(java)

    时间复杂度为O( n^(3/2) )不是一个稳定的排序算法 如何看一个算法是否稳定:{("scala",12),("python",34),("c++ ...

  3. 学习笔记:delphi之TStringGrid

    1.说明 最近加入了一个项目组,使用的开发工具是delphi6,想想又要开始搞这个工具有点小忧伤,但没办法谁让咱就是个打杂的尼... 的需求是显示一个类似于Word/excel的那种表格,可以合并列等 ...

  4. Xamarin.Android之ContentProvider

    一.前言 掌握了如何使用SQLiteOpenHelper之后,我们就可以进行下一步的学习.本章我们将会学习如何使用ContentProvider来将数据库方面的操作封装起来,同时它还可以供其他应用访问 ...

  5. OAuth2 理解

    OAth2 是为了某个应用向第三方应用开放服务时,控制权限的. 因为不可以直接将账户体系开放出去,要求重新登录. 其实本质是让用户在客户端来判断是否要给该应用开放平台的权限,如果用户同意,那么可以拿到 ...

  6. Marmoset Toolbag中的角色布光技巧 by Joe”EarthQuake”Wilson

    Sagat by Tim “spacemonkey” Appleby 有言在先 首先,我要感谢才华横溢的Tim“spacemonkey Appleby允许本教程中使用他那个极其NB的Sagat模型.不 ...

  7. MySQL中You can't specify target table for update in FROM clause一场

    mysql中You can't specify target table <tbl> for update in FROM clause错误的意思是说,不能先select出同一表中的某些值 ...

  8. maven repo plugin archiver

    Failure to transfer org.apache.maven:maven-archiver:pom:2.4.1 from http://repo1.maven.org/xxx was ca ...

  9. Docker之Compose服务编排

    Compose是Docker的服务编排工具,主要用来构建基于Docker的复杂应用,Compose 通过一个配置文件来管理多个Docker容器,非常适合组合使用多个容器进行开发的场景. 说明:Comp ...

  10. 自定义view(一)

    最近在学习自定义view  一遍看一别学顺便记录一下 1.View的测量-------->onMeasure() 首先,当我们要画一个图形的时候,必须知道三个数据:位置,长度,宽度   才能确定 ...