07 内部类闭包的变量是如何生成的?

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了07 内部类闭包的变量是如何生成的?相关的知识,希望对你有一定的参考价值。

前言

呵呵呵 很久以前的一个问题了, 当时写了 case, 但是 没有时间来看 

问题来自于 hllvmgroup [讨论] 关于Local Class可以访问有final关键词修饰的local variables , R 大也是做了比较详尽的说明  

我们这里主要是 看一下 javac 中的一些具体的体现 

引用 R大的一部分回复  
 

方法内部类(包括local class、anonymous class之类)可以访问final修饰的外部变量或参数,这是Java的非常弱的闭包的功能。

这功能在Java里是纯语言层面的,在JVM里没有任何特殊支持。也就是说,这个功能在把Java源码编译到Class文件的过程中就已经解除语法糖变成很一般的东西,JVM不需要任何特别的实现就可以支持它的运行。

Chapter 8. Classes

Java Language Specification, Java SE 7 Edition, 8.1.3 写道

Any local variable, formal parameter, or exception parameter used but not declared in an inner class must be declared final.

Any local variable used but not declared in an inner class must be definitely assigned (§16) before the body of the inner class.



具体来说,之所以设计成只能捕获final的变量或参数,就是因为final的话capture-by-value和capture-by-reference的效果会是一样的,用户无法分辨语言实际是采用哪种办法实现的。

实际上Java实现楼主的例子是类似这样:

Java代码 

  1. class Rectangle   
  2.   public int calculateArea(final int w, final int h)   
  3.     postTask(new Rectangle$1(w, h));  
  4.     
  5.   
  6.   
  7. class Rectangle$1 implements Runnable   
  8.   private int w$;  
  9.   private int h$;  
  10.   
  11.   Rectangle$1(int w, int h)   
  12.     this.w$ = w;  
  13.     this.h$ = h;  
  14.     
  15.   
  16.   public void run()   
  17.     writeToFile(this.w$ * this.h$);  
  18.     
  19.   


也就是用了capture-by-value的实现方式:闭包(这里是内部类)捕获的外部变量的值在创建闭包实例时被拷贝到了闭包实例里。

要体现capture-by-reference的效果,那语言就必须允许捕获非final的变量,然后对变量的值进行修改。那就得用更繁琐的实现方式了。

以下测试用例效果, 以及截图基于 jdk8

测试用例

/**
 * Test28VisitVarInEnclosingScope
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2020-03-01 17:17
 */
public class Test28VisitVarInEnclosingScope 

    // Test28VisitVarInEnclosingScope
    public static void main(String[] args) 

        calculateArea(1, 4);

    

    // calculateArea
    public static int calculateArea(final int w, final int h) 
        return postTask(new Runnable() 
            public void run() 
                todo(w * h);
            
        );
    

    // postTask
    public static int postTask(Runnable runnable) 
        System.out.println(" postTask ");
        runnable.run();
        return 0;
    

    // todo0
    public static void todo(int x) 
        System.out.println(" todo : " + x);
    


Test28VisitVarInEnclosingScope$1 内部类的字节码信息反编译如下

master:21_javac jerry$ javap -c Test28VisitVarInEnclosingScope\\$1.class 
Compiled from "Test28VisitVarInEnclosingScope.java"
final class Test28VisitVarInEnclosingScope$1 implements java.lang.Runnable 
  final int val$w;

  final int val$h;

  Test28VisitVarInEnclosingScope$1(int, int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #1                  // Field val$w:I
       5: aload_0
       6: iload_2
       7: putfield      #2                  // Field val$h:I
      10: aload_0
      11: invokespecial #3                  // Method java/lang/Object."<init>":()V
      14: return

  public void run();
    Code:
       0: aload_0
       1: getfield      #1                  // Field val$w:I
       4: aload_0
       5: getfield      #2                  // Field val$h:I
       8: imul
       9: invokestatic  #4                  // Method Test28VisitVarInEnclosingScope.todo:(I)V
      12: return

javac 中的体现 

这里会拆分成两个问题 

1. 如何找到这部分闭包的变量 ?

2. 哪里吧变量个添加到 classDef 的 ?

1. 如何找到这部分闭包的变量 ?

在方法 calculateArea 作为上下文, 查询 Test28VisitVarInEnclosingScope$1 引用的外部变量  

是否是外部变量的标准是 符号的owner 为 calculateArea, 并且不是 字面量 

2. 哪里吧变量个添加到 classDef 的 ?

如下地方将这两个变量添加到了 classDef 

构造方法的处理是在这里处理的, 在吧变量个添加到 classDef 之前

完 

参考

[讨论] 关于Local Class可以访问有final关键词修饰的local variables

以上是关于07 内部类闭包的变量是如何生成的?的主要内容,如果未能解决你的问题,请参考以下文章

07 内部类闭包的变量是如何生成的?

闭包与内部类

python函数的闭包

javascript闭包

ChatGPT问答[2]-Python类中的方法是闭包吗?是否拥有闭包的性质?

vue下载文件动态生成的a标签必须带上文件名吗