Java:匿名类的初始化和构造函数

标签 java constructor javac anonymous-class javap

我想了解我在处理匿名类时遇到的一种奇怪行为。

我有一个在其构造函数中调用 protected 方法的类(我知道,设计不佳,但那是另一回事......)

public class A {
  public A() {
    init();
  }
  protected void init() {}
}

然后我有另一个扩展 A 并覆盖 init() 的类。

public class B extends A {
  int value;
  public B(int i) {
    value = i;
  }
  protected void init() {
    System.out.println("value="+value);
  }
}

如果我编码

B b = new B(10);

我明白了

> value=0

这是意料之中的,因为父类(super class)的构造函数在 B 构造函数之前被调用,然后 value 仍然存在。

但是当使用这样的匿名类时

class C {
  public static void main (String[] args) {
    final int avalue = Integer.parsetInt(args[0]);
    A a = new A() {
      void init() { System.out.println("value="+avalue); }
    }
  }
}

我希望得到 value=0 因为这应该或多或少等于类 B:编译器自动创建一个新类 C$1 扩展 A 并创建实例变量来存储在匿名类的方法中引用的局部变量,模拟闭包等...

但是当你运行这个时,我得到了

> java -cp . C 42
> value=42

最初我认为这是因为我使用的是 java 8,也许在引入 lamdbas 时,他们改变了匿名类在引擎盖下实现的方式(你不再需要 final), 但我也尝试使用 java 7 并得到了相同的结果...

其实用javap看字节码,可以看出B

> javap -c B
Compiled from "B.java"
public class B extends A {
  int value;

  public B(int);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method A."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #2                  // Field value:I
       9: return
...

对于C$1:

> javap -c C\$1
Compiled from "C.java"
final class C$1 extends A {
  final int val$v;

  C$1(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #1                  // Field val$v:I
       5: aload_0
       6: invokespecial #2                  // Method A."<init>":()V
       9: return
....

有人能告诉我为什么会有这种差异吗? 有没有办法使用“普通”类复制匿名类的行为?

编辑: 澄清这个问题:为什么匿名类的初始化会破坏任何其他类的初始化规则(在设置任何其他变量之前调用 super 构造函数)? 或者,有没有办法在调用 super 构造函数之前在 B 类中设置实例变量?

最佳答案

这个问题适用于所有内部类,而不仅仅是匿名类。 (匿名类是内部类)

JLS 不规定内部类主体如何访问外部局部变量;它只有specifies局部变量实际上是最终的,并且在内部类主体之前明确分配。所以按理说内部类肯定是看到了局部变量的明确赋值。

JLS 没有具体说明内部类如何看待该值;由编译器使用任何技巧(在字节码级别上可能)来实现该效果。特别是,这个问题与构造函数完全无关(就语言而言)。

类似的问题是内部类如何访问外部实例。这有点复杂,它确实有 something与构造函数有关。尽管如此,JLS 仍然没有规定编译器如何实现它;该部分包含一条评论,“...编译器可以随心所欲地表示直接封闭的实例。Java 编程语言不需要...”


从 JMM 的角度来看,这种规范不足可能是个问题;目前尚不清楚内部类中的写入与读取相关的方式。可以合理地假设,写是在一个合成变量上完成的,它在(按编程顺序)new InnerClass() 操作之前;内部类读取合成变量以查看外部局部变量或封闭实例。


Is there a way to replicate the behavior of the anonymous class using "normal" classes?

您可以将“普通”类安排为外-内类

public class B0
{
    int value;
    public B0(int i){ value=i; }

    public class B extends A
    {
        protected void init()
        {
            System.out.println("value="+value);
        }
    }
}

它将像这样使用,它打印出 10

    new B0(10).new B();

可以添加一个方便的工厂方法来隐藏语法的丑陋

    newB(10);

public static B0.B newB(int arg){ return new B0(arg).new B(); }

所以我们把类(class)分成两部分;外部甚至在 super 构造函数之前执行。这在某些情况下很有用。 (another example)


(内部匿名访问局部变量包含实例有效的最终 super 构造函数)

关于Java:匿名类的初始化和构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32463571/

相关文章:

git - 错误 : unmappable character for encoding UTF8 after GIT merge

ant - 用于 Javac 任务的 Ant 中的 ClassPath

java - Process.getInputStream() 使用哪种编码?

java - 如何使 "Enter"键的行为类似于在 JFrame 上提交

java - Swing - Thread.sleep() 停止 JTextField.setText() 工作

javascript - Dojo 无参数构造函数调用带参数的基础构造函数

c++ - 具有专门构造函数的模板类

java - 在 PreparedStatement 中重用参数?

javascript - 将函数直接分配给构造函数与原型(prototype)分配有什么区别,为什么?

在 Mac 上使用 Java 7 安装 javac 无效目标 1.7 mvn