我一直在玩弄ASM,我相信我成功地将 final 修饰符添加到类的实例字段中;但是,然后我继续实例化所述类并在其上调用 setter,这成功地更改了 now-final 字段的值。我的字节码更改是否有问题,或者最终仅由 Java 编译器强制执行?
更新:(7 月 31 日)这里有一些代码给你。主要部分是
- 一个带有
private int x
andprivate final int y
的简单 POJO - MakeFieldsFinalClassAdapter,它使得它访问的每个字段都是最终的,除非它已经是,
- 和 AddSetYMethodVisitor,它会导致 POJO 的 setX() 方法也将 y 设置为与设置 x 相同的值。
换句话说,我们从一个具有一个 final (x) 和一个 non-final (y) 字段的类开始。我们使 x 最终。除了设置 x 之外,我们还让 setX() 设置 y。我们跑。x 和 y 都设置没有错误。代码在github 上。您可以使用以下命令克隆它:
git clone git://github.com/zzantozz/testbed.git tmp
cd tmp/asm-playground
有两件事需要注意:我首先提出这个问题的原因是:我设置为 final 的字段和已经设置为 final 的字段都可以使用我认为是正常的字节码指令进行设置。
另一个更新:(8 月 1 日)用 1.6.0_26-b03 和 1.7.0-b147 测试,结果相同。也就是说,JVM 在运行时愉快地修改了 final 字段。
最终(?)更新:(9 月 19 日)我从这篇文章中删除了完整的源代码,因为它相当长,但它仍然可以在 github 上找到(见上文)。
我相信我已经最终证明JDK7 JVM 违反了规范。(参见Stephen's answer 中的摘录。)在使用 ASM 修改字节码后,如前所述,我将其写回到类文件中。使用优秀的JD-GUI,这个类文件反编译成如下代码:
package rds.asm;
import java.io.PrintStream;
public class TestPojo
{
private final int x;
private final int y;
public TestPojo(int x)
{
this.x = x;
this.y = 1;
}
public int getX() {
return this.x;
}
public void setX(int x) {
System.out.println("Inside setX()");
this.x = x; this.y = x;
}
public String toString()
{
return "TestPojo{x=" +
this.x +
", y=" + this.y +
'}';
}
public static void main(String[] args) {
TestPojo pojo = new TestPojo(10);
System.out.println(pojo);
pojo.setX(42);
System.out.println(pojo);
}
}
简单看一下应该会告诉您,由于重新分配了 final 字段,该类将永远不会编译,但是在普通的 JDK 6 或 7 中运行该类看起来像这样:
$ java rds.asm.TestPojo
TestPojo{x=10, y=1}
Inside setX()
TestPojo{x=42, y=42}
- 在我报告这方面的错误之前,还有其他人有意见吗?
- 谁能确认这应该是 JDK 6 中的错误还是仅 7 中的错误?