私はASMをいじってきましたが、クラスのインスタンス フィールドに final 修飾子を追加することに成功したと思います。ただし、その後、上記のクラスをインスタンス化し、セッターを呼び出して、現在の最終フィールドの値を正常に変更しました。バイトコードの変更に何か問題がありますか、それとも最終的には Java コンパイラによってのみ強制されますか?
更新: (7 月 31 日) ここにいくつかのコードがあります。主なパーツは
private int x
とprivate final int y
を含む単純な POJO- MakeFieldsFinalClassAdapter は、アクセスするすべてのフィールドを最終的なものにします。
- AddSetYMethodVisitor は、POJO の setX() メソッドによって、x に設定した値と同じ値に y も設定します。
つまり、1 つの最終 (x) フィールドと 1 つの非最終 (y) フィールドを持つクラスから始めます。x を final にします。x の設定に加えて、setX() で y も設定します。私たちは走る。x と y の両方がエラーなしで設定されます。コードは github にあります。次の方法で複製できます。
git clone git://github.com/zzantozz/testbed.git tmp
cd tmp/asm-playground
2 つの注意事項:そもそもこの質問をした理由: final にしたフィールドと既に final になっているフィールドの両方に、通常のバイトコード命令であると思われるものを設定できます。
別の更新: (8 月 1 日) 1.6.0_26-b03 と 1.7.0-b147 の両方でテストされ、同じ結果が得られました。つまり、JVM は実行時に最終フィールドを問題なく変更します。
最終(?) 更新: (9 月 19 日) かなり長いので、この投稿から完全なソースを削除しますが、github では引き続き利用できます (上記参照)。
私は、JDK7 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);
}
}
これを少し見てみると、 final フィールドを再割り当てするためにクラスがコンパイルされないことがわかりますが、プレーンなバニラ JDK 6 または 7 でそのクラスを実行すると、次のようになります。
$ java rds.asm.TestPojo
TestPojo{x=10, y=1}
Inside setX()
TestPojo{x=42, y=42}
- これに関するバグを報告する前に、他の誰かが意見を持っていますか?
- これがJDK 6のバグなのか、それとも7だけのバグなのか、誰でも確認できますか?