====
以下のようなライブラリ・コードを書き、コンパイルして foo.jar に丸めておく。
package p1; public class Foo { public String bar() { return "hello"; } }で、このライブラリを使う以下のようなクライアントコードを書いて、-cp に foo.jar を指定してコンパイルする。
package p2; import p1.Foo; public class Client { public static void main(String[] args) { System.out.println(new Foo().bar()); } }
コンパイルされたクラスを java コマンドで -cp に foo.jar を指定して実行すると、標準出力に hello と出力される。
さて、ここで クラス Foo を変更して、つまり foo.jar の中身を変えて、再コンパイルせずに再度
Client#main()
を実行したらどうなるか。
(1) メソッドの中身を変えてみる
public String bar() { return "good-bye"; }問題無し。標準出力に good-bye と表示される。
(2) メソッドの名前を変えてみる
public String Bar() { return "hello"; }メソッド p1.Foo.bar()Ljava/lang/String が見当たらないって事で、NoSuchMethodError が送出される。リフレクションのコーディングでよく catch したりするNoSuchMethodExceptionではなく、NoSuchMethodError が投げられてきた。
(3) メソッドの引数を変えてみる
public String Bar(String s) { return "hello"; }これは (2) と同じ
(4) メソッドの戻り値を変えてみる
public Object Bar() { return "hello"; }これも (2) と同じ。戻り値も識別される。
(5) throws を追加してみる
public String bar() throws IOException { throw new IOException("test"); }これは意外にも普通に実行されて、IOExceptionが送出されてスタックトレースされる。意外というのは、これを再コンパイルしようとすると、コンパイルエラーが出るからで、Client#main() に throws を追加するか、bar() を try-catch で囲むかしないと、コンパイルが通らない。でも、コンパイルは通らなくても実行時のメソッド呼び出しは成功する。
(6) メソッドの可視性を変えてみる
private String bar() { return "hello"; }これは IllegalAccessError が送出される。つまり、一応 p1.Foo.bar()Ljava/lang/String の存在は識別された上で、アクセスに失敗して例外が発生した模様。
(7) クラスの可視性を変えてみる
class Foo { …(6) と同様だが、クラス p1.Foo の存在を識別したあとに、アクセスするところで失敗している。
(8) 定数を変えてみる
メソッドは以上のような感じで、フィールドもだいたい想像がつく。で、ここでちょっと定数を試してみる事にする。
まずライブラリコードを以下のように変える。
package p1; public class Foo { public static final int C = 100; }次に、クライアントコードを以下の様に変える
package p2; import p1.Foo; public class Client { public static void main(String[] args) { System.out.println(Foo.C); } }
両方コンパイルして実行すると、標準出力に100が実行される。
ここでライブラリコードを以下の様に修正し、jar を作り直す。
public static final int C = 200;で、クライアントコードを再実行すると、標準出力に 200 が出力されると思いきや、実際には変更前と同じ100が出力される。
javap で見てみると、バイトコードに定数 100 が埋め込まれているのがわかる。なるほど定数はこういう扱いらしい。
public static void main(java.lang.String[]); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: bipush 100 5: invokevirtual #3; //Method java/io/PrintStream.println:(I)V 8: return
(9) null値 を試してみる
ライブラリ・コード を以下のように変更して、両方とも再コンパイルして実行してみる。package p1; public class Foo { public static final String C = null; }標準出力には、null が出力される。
ここでライブラリ・コードを、public static final String C = "a"; に変更して、クライアントを実行してみる。
(8) の結果から、null が出力される結果を類推してしまうが、実際には "a" が出力される。理由は、Java 言語仕様として null 定数として扱われないためコンパイル時には byte コードに直接書き込まれず、実行時に初めて Foo.Cの値を読むことになるかららしい。javap は以下のようになる。public static void main(java.lang.String[]); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #3; //Field p1/Foo.C:Ljava/lang/String; 6: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 9: return
0 件のコメント:
コメントを投稿