运行时是“最终”决赛吗?

我一直在玩ASM ,我相信我成功地将final修饰符添加到了类的实例字段中; 然后我继续实例化所述类并在其上调用一个setter,它成功地改变了now-final字段的值。 我的字节码更改有问题,还是仅由Java编译器最终强制执行?

更新: (7月31日)这里有一些代码供您使用。 主要部分是

  1. 一个带有private int xprivate final int y的简单POJO,
  2. MakeFieldsFinalClassAdapter,它使得它访问的每个字段都是final,除非它已经是,
  3. 和AddSetYMethodVisitor,它导致POJO的setX()方法也将y设置为它设置为x的相同值。

换句话说,我们从一个带有一个final(x)和一个非final(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在运行时愉快地修改了最终字段。

最终(?)更新: (9月19日)我正在从这篇文章中删除完整的源代码,因为它相当冗长,但它仍然可以在github上看到(见上文)。

我相信我已经最终certificateJDK7 JVM违反了规范 。 (请参阅Stephen的回答摘录 。)如前所述,在使用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} 
  1. 在我报告此错误之前,有没有其他人有输入?
  2. 任何人都可以确认这应该是JDK 6中的错误还是只有7?

运行时是“最终”决赛吗?

不是你的意思。

AFAIK, final修饰符的语义只能由字节码编译器强制执行。

没有用于初始化final字段的特殊字节码,字节码validation器(显然)也不检查“非法”分配。

但是,JIT编译器可能会将final修饰符视为不需要重新获取内容的提示。 因此,如果您的字节码修改标记为final的变量,则可能会导致不可预测的行为。 (如果你使用reflection修改final变量,也会发生同样的事情。规范清楚地说明了……)

当然,您可以使用reflection修改final字段。


UPDATE

我看了一下Java 7 JVM规范,这部分与我上面所说的相矛盾。 具体来说,PutField操作码的描述说:

“链接exception…否则,如果字段是final,则必须在当前类中声明,并且该指令必须在当前类的实例初始化方法( )中发生。否则,将IllegalAccessError 。 “

因此,虽然您(理论上)可以在对象的构造函数中多次分配final字段,但字节码validation程序应该阻止任何尝试加载包含分配给final字节码的方法。 哪个……当你考虑Java安全沙箱时……是一件好事。

如果该字段是最终字段,则在分配时可能仍然存在这种情况。 例如在构造函数中。 此逻辑由编译器强制执行,如本文所述 。 JVM本身不会强制执行此类规则,因为性能价格太高,字节代码validation程序可能无法轻松确定字段是否仅分配一次。

所以通过ASM让这个领域final ,可能没什么意义。

您可以使用reflection在运行时覆盖最终字段。 在将JSON绑定到Java对象时,Gson会一直这样做。