Javaのfinal staticな変数でのちょっとした罠

Javaは、クラスの中に変数がある。
クラスというスコープの中で管理されていると思いきや、実はちょっとした罠がある。

細かすぎて伝わらない罠だし、ちゃんと自動テストしてビルドして配布するという人権を尊重した環境なら問題は起きない。

まずは実験を見てくれ

Teisu.java という定数だけあるクラス
定数はstatic final

package te;

public class Teisu {
	public static final String HOGEHOGE = "GAMERA DAISUKI";
}

上記の定数にアクセスしているStartKoKoKara.javaも作る。

package st;
import te.Teisu;

public class StartKoKoKara {
    public static void main(String[] args) {
		System.out.println(Teisu.HOGEHOGE);
	}
}

この状態で、Javaで実行してみれば当然GAMERA DAISUKIと表示される

$ java st.StartKoKoKara 
GAMERA DAISUKI

さて、これらのクラスファイルは別の場所に置いてあるとしよう。
たとえばサーバーでも何でも、ソースを編集したらコンパイルされるような場所ではなく、修正したクラスファイルを置き換える運用をしていると想定する。

その上で、Teisuクラスの定数を変更した。

package te;

public class Teisu {
	public static final String HOGEHOGE = "OKANE KURERUNARA SIGOTOSITEMOIIYO";
}

コンパイルされたTeisuのクラスファイルだけをコピー

そして実行をしてみると…

$ java st.StartKoKoKara 
GAMERA DAISUKI

変更が反映されていない!!

原因はこれだ

jadという逆コンパイラを使って、クラスファイルからjavaファイルに戻してみよう。

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Teisu.java

package te;


public class Teisu
{

    public Teisu()
    {
    }

    public static final String HOGEHOGE = "OKANE KURERUNARA SIGOTOSITEMOIIYO";
}

修正はされている
しかし、これの参照先を逆コンパイルしてみよう

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   StartKoKoKara.java

package st;

import java.io.PrintStream;

public class StartKoKoKara
{

    public StartKoKoKara()
    {
    }

    public static void main(String args[])
    {
        System.out.println("GAMERA DAISUKI");
    }
}

お分かりいただけただろうか。

そう、static finalのStringはクラスファイルになったときに、不変だからということでベタ書きに書き換わっている。おそらくコンパイラが最適にした結果なんだろう。

だから、更新したクラスのクラスファイルだけ置き換えてもこのように更新前の値が残ってしまうことになる。

呼び出し元だけじゃなくて呼び出し先のクラスも置き換えしなくちゃならない
極論をいえば呼び出し先だけ置き換えてもOK

呼び出し先はどうなっている?

Javaファイルを更新したあとEclipseでコンパイルした結果を見てみる

[hogeuser@hogemachine bin]$ ls -lR
.:
合計 12
-rw-r--r-- 1 hogeuser hogeuser  468 12月 20 00:15 Nanika.class
drwxr-xr-x 2 hogeuser hogeuser 4096 12月 20 00:14 st
drwxr-xr-x 2 hogeuser hogeuser 4096 12月 20 00:15 te

./st:
合計 4
-rw-r--r-- 1 hogeuser hogeuser 570 12月 20 00:23 StartKoKoKara.class

./te:
合計 4
-rw-r--r-- 1 hogeuser hogeuser 349 12月 20 00:23 Teisu.class
[hogeuser@hogemachine bin]$ 

Teisuと共にStartKoKoKaraも同時にコンパイルされている。
全然関係ないクラスであるNanikaはコンパイルされていない。

まとめてコンパイルされるので、もしクラスファイルの更新差分だけを持っていく運用を続けたいのであれば、最終更新日をみて更新されたっぽいやつは全部持っていくといいと思う。

スポンサードリンク

関連コンテンツ