“最终”在运行时是最终的吗?
我一直在玩ASM,我相信我成功地将最终修饰符添加到类的实例字段;但是,然后我继续实例化所述类并对其调用一个 setter,该 setter 成功更改了现在最终字段的值。我是否在字节码更改时出错,或者最终仅由 Java 编译器强制执行?
更新:(7月31日)这里有一些代码给你。主要部件有
- 一个简单的 POJO 与 和 ,
private int x
private final int y
- MakeFieldsFinalClassAdapter,它使它访问的每个领域都成为最终的,除非它已经是,
- 和 AddSetYMethodVisitor,这会导致 POJO 的 setX() 方法也将 y 设置为与它设置 x 相同的值。
换句话说,我们从一个具有一个最后(x)和一个非最终(y)字段的类开始。我们使 x 成为最终结果。除了设置 x 之外,我们还将 setX() 设置为 y。我们运行。x 和 y 都设置无错误。代码在github上。您可以使用以下命令克隆它:
git clone git://github.com/zzantozz/testbed.git tmp
cd tmp/asm-playground
需要注意的两点:我首先问这个问题的原因是:我最终创建的字段和已经最终的字段都可以使用我认为是正常的字节码指令进行设置。
另一个更新:(8 月 1 日)使用 1.6.0_26-b03 和 1.7.0-b147 进行测试,结果相同。也就是说,JVM 在运行时愉快地修改最终字段。
Final(?)更新:(19 Sep)我从这篇文章中删除了完整的源代码,因为它相当冗长,但它仍然可以在github上使用(见上文)。
我相信我已经最终证明了JDK7 JVM违反了规范。(参见斯蒂芬答案中的摘录。如前所述使用 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);
}
}
简单地看一下应该会告诉你,由于重新分配了最终字段,类永远不会编译,但是在普通的普通JDK 6或7中运行该类看起来像这样:
$ java rds.asm.TestPojo
TestPojo{x=10, y=1}
Inside setX()
TestPojo{x=42, y=42}
- 在我报告这方面的错误之前,是否有其他人有意见?
- 谁能确认这应该是JDK 6中的错误还是仅在7中?