>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 30596 个阅读者 刷新本主题
 * 贴子主题:  java内部类final语义实现 回复文章 点赞(0)  收藏  
作者:javathinker    发表时间:2016-10-17 19:24:57     消息  查看  搜索  好友  复制  引用

原文出处: i flym  https://www.iflym.com/index.php/java-programe/201505310001.html

本文描述在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();
即此信息是由编译器引入的。

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


程序猿的技术大观园:www.javathinker.net
  Java面向对象编程-->面向对象开发方法概述之开发思想(上)
  JavaWeb开发-->访问数据库(Ⅰ)
  JSP与Hibernate开发-->Java对象持久化技术概述
  Java网络编程-->Socket用法详解
  精通Spring-->Vue简介
  Vue3开发-->CSS过渡和动画
  Java函数式接口和Stream流
  Java虚拟机(JVM)参数配置说明
  不修改源代码,动态注入Java代码的方法
  [讨论]书中多线程章节的语言表述有误?
  关于新书
  HashMap中 get 和 put 操作的具体过程
  Synchronized与ReentrantLock区别总结
  正则表达式范例
  Java设计模式:观察者模式
  Java Scoket之java.io.EOFException解决方案
  Java 入门实用代码:汉诺塔算法
  Java入门实用代码:获取数组长度
  正则表达式【匹配非字母和数字】
  jdbc连接各种数据库代码
  初学者该学哪种编程语言
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。