【问题标题】:What happens to variables/objects in inner classes of function objects?函数对象的内部类中的变量/对象会发生什么?
【发布时间】:2018-07-02 11:24:32
【问题描述】:

我有一个函数multi2,它返回内部类Inner 作为Object

a 会发生什么 - 它保存在哪里以及如何访问它?

public class C {
    private static Object multi2(final int a) {
        class Inner {
            public int hashCode() {
                return 2*a;
            }
        }
        return new Inner();     // What happens to a?
                                // Who allocates a?
                                // Can I Access a?
    }

    public static void main(String[] args) {
        Object o = multi2(6);
        System.out.println("o.hashCode() = " + o.hashCode());

        o = multi2(4);
        System.out.println("o.hashCode() = " + o.hashCode());
    }
}

【问题讨论】:

    标签: java inner-classes function-object


    【解决方案1】:

    在实现级别发生的情况是a 值的副本保存在C.Inner 类的编译版本中声明的合成实例变量 中。

    a 的值通过一个额外的参数传递给编译后的Inner 构造函数。

    C.Inner.hashCode 方法使用合成变量的值。访问Inner.hashCode的源代码中的a转化为访问编译代码中对应的综合变量。

    外部作用域中的变量必须是final1。合成变量必须是 Inner 类中的 final2。这维持了一种错觉,即(可能)Inner 类的多个实例看到相同的a 变量。 (它们不是,但由于变量无法更改,因此内部类的代码无法区分。)

    如果您使用javap 查看已编译示例的字节码,您将看到用于在外部类和内部类中实现这一点的机制。


    1 - 或从 Java 8 开始有效。

    2 - 如果a 可以被Inner 方法改变,那么两个具有相同外部类的Inner 实例需要共享一个可变变量,其生命周期(现在)比堆栈帧长一个multi2 电话。这需要以某种方式将a 从堆栈变量转换为存在于堆上的东西。这将是昂贵和复杂的。

    【讨论】:

    • 太好了,您能否添加有关尝试访问a 变量的解释,例如@Clayton 答案。
    【解决方案2】:

    你已经在函数内部定义了class Inner,所以类的范围是 限制在方法中。而且您的 函数是静态的,因此只要加载了类定义,它就会一直存在。您已经覆盖了 InnerClass 中的 hashCode 函数,因此每次调用 multi2(param) 时,您都在为 InnerClass 的实例创建 hashCode 并返回 hashCode 的实例strong>InnerClass。

    所以关于你的问题,如果我错了,请纠正我。

    a 会发生什么? a 在你的静态方法的范围内,所以只要类定义被加载,它就会一直存在。

    谁分配一个? a 的范围被限制在静态方法内部,静态方法不需要实例来访问它,但至于静态方法/变量分配,我认为它取决于 JVM。

    我可以访问吗? 不,您不能从静态方法外部访问a,它在您的静态方法中受到限制。

    【讨论】:

      【解决方案3】:

      由于“a”是一个本地参数,您可以使用不同的方法来读取“a”值:

      public class C {
      
        public static Object multi2(final int a) {
          return new Inner(a);
        }
      
        public static void main(String[] args) {
          Object o = multi2(6);
          System.out.println("o.hashCode() = " + o.hashCode());
          System.out.println("o.getA() = " + ((Inner) o).getA());
      
          o = multi2(4);
          System.out.println("o.hashCode() = " + o.hashCode());
          System.out.println("o.getA() = " + ((Inner) o).getA());
        }
      }
      
      class Inner{
        public int valueA;
      
        public Inner(int a)
        {
          valueA = a;
        }
      
        public int getA() {
          return valueA;
        }
      
        public int hashCode() {
          return 2*valueA;
        }
      }
      

      【讨论】:

        【解决方案4】:

        我想知道实际发生了什么,所以我编译了你的代码并查看了字节码输出。

        基本上,编译器会在你的类“Inner”中添加一个构造函数。它还向采用“a”的构造函数添加了一个参数。如果您的 multi2() 方法不是静态的,那么可能还有一个参数来获取“this”,其中“this”是 multi2() 正在执行的“C”的实例。但是由于我们处于静态上下文中,因此没有“this”。

        编译器将私有 final 字段添加到您的类“Inner”,并使用通过构造函数传递的值设置该私有字段。编译器也会转换

            new Inner()
        

        进入

            new Inner(a)
        

        Hashcode 然后访问包含 a 值的私有字段。

        如果 'a' 是对象而不是基元,则方法相同,但将传递引用而不是实际数值。

        你如何访问这个变量?好吧,您可以通过反射访问它,但是有很多问题:

        1) 你不知道编译器生成的字段名称,所以只能通过查看字节码来获取名称。不要相信反编译器,因为它们可能会更改名称。您必须自己查看字节码才能找到答案。

        2) 编译器可能将该字段标记为 final,这意味着即使您可以通过反射来为您访问该字段,您也无法对其进行更新。

        3) 完全由编译器决定字段名称。根据编译器及其心情,字段名称可能会在构建之间发生变化。

        【讨论】:

          【解决方案5】:

          Inner 是一个所谓的本地类a 是传递给方法 multi2 的参数,可在该范围内访问。在该方法之外,您无法访问a

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-06
            • 1970-01-01
            • 2012-07-23
            • 1970-01-01
            • 1970-01-01
            • 2016-10-17
            相关资源
            最近更新 更多