日々常々

ふつうのプログラマがあたりまえにしたいこと。

classファイルを触ってはいけない

(注意)触ると言ってもバイナリエディタで触るとかではありません!


まずこういうクラスがあります。

// Hoge.java
class Hoge {
	static final String foo = "foo";
	static String bar = "bar";
	static final Object baz = "baz";
}

そして、これらのフィールドを使うクラスがあります。

// Fuga.java
public class Fuga {
	public static void main(String[] args) {
		System.out.println(Hoge.foo);
		System.out.println(Hoge.baz);
		System.out.println(Hoge.bar);
	}
}

そのままコンパイルします。

Airof:temp irof$ ls
Fuga.java	Hoge.java
Airof:temp irof$ javac *
Airof:temp irof$ ls
Fuga.class	Fuga.java	Hoge.class	Hoge.java

実行します。

Airof:temp irof$ java Fuga
foo
baz
bar

普通ですね。

では Hoge.java を変えます。

class Hoge {
	static final String foo = "FOOFOO";
	static String bar = "BARBAR";
	static final Object baz = "BAZBAZ";
}

Hoge.javaコンパイル

Airof:temp irof$ javac Hoge.java

そんで実行。

Airof:temp irof$ java Fuga
foo
BAZBAZ
BARBAR

知ってれば予想通りですかね。知らなければなんぞこれになるかもしれません。

わからない人は Fuga の javap すれば何となくわかるかも。

Compiled from "Fuga.java"
public class Fuga {
  public Fuga();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String foo
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: getstatic     #5                  // Field Hoge.baz:Ljava/lang/Object;
      14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      20: getstatic     #7                  // Field Hoge.bar:Ljava/lang/String;
      23: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: return        
}

とりあえず、コンパイル後のclassファイルたちを触っちゃいけないです。通常はビルドツール使ってまとめてやるだろうから大丈夫なんだろうけども、まとめてコンパイルして、まとめてjarに入れるとかするべきです。


たまに特定のclassファイルだけ差し替えたり、選択してjar作ったりしてるところもあったりします。そう言うところではたまに ClassNotFoundException が出たりします。
例えば、上記の例で Hoge.class を削除して Fuga を実行すると出ます。

Airof:temp irof$ java Fuga
foo
Exception in thread "main" java.lang.NoClassDefFoundError: Hoge
	at Fuga.main(Fuga.java:4)
Caused by: java.lang.ClassNotFoundException: Hoge
	at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
	... 1 more

foo は出力されるんですよね。こんなトラブルの調査結果が「Hoge.foo は使えるのに Hoge.baz は使えない。原因不明。」とかになったりする。それも困った話ですけど、そもそもclassファイルを直接触らなきゃ起こらない話で。
素直にビルドツールを使えば良い話なんですけども。