「Java」同名クラス ( 同名パッケージ) が存在する場合にどのクラスが使用されるか / URLClassLoader について

例えば、以下みたいに同名パッケージで同名のクラスが存在する場合にどのクラスが使用されるのかというのをちょっとみてみました。結論を言うと、クラスパスでより前に指定したものが使われるという感じみたいです ( 該当するクラスが見つかった時点でそいつが使われるという感じですかね。たぶん )。

■ test1.jar としてパッケージング

package test;

public class Test {
  public String test() {
    return "11111";
  }
}

■ test2.jar としてパッケージング

package test;

public class Test {
  public String test() {
    return "22222";
  }
}

■ jar にはパッケージングせず

package test;

public class Test {
  public String test() {
    return "33333";
  }
}

■ Main クラス

import test.*;

class Main {
  public static void main(String[] args) {
    System.out.println(new Test().test());
  }
}

※ 一応ディレクトリ構成

[root@centos62 jarLoad]# ls -R
.:
Main.class  Main.java  test  test1.jar  test2.jar

./test:
Test.class  Test.java

■ 実行結果

[root@centos62 jarLoad]# java -cp .:test1.jar:test2.jar Main
33333
[root@centos62 jarLoad]# java -cp test1.jar:test2.jar:. Main
11111
[root@centos62 jarLoad]# java -cp test2.jar:.:test1.jar Main
22222

verbose:class でロードされたクラスを見てみる。以下の場合は "33333" 返す Test.java のみがロードされてることがわかる。

[root@centos62 jarLoad]# java -cp .:test1.jar:test2.jar -verbose:class Main | grep Test
[Loaded test.Test from file:/usr/local/tmp/java/jarLoad/]


クラスパスに指定した順に関係なく、任意のクラス使いたいって場合は URLClassLoader が使えるみたいです。

・URLClassLoader (Java Platform SE 8 )
http://docs.oracle.com/javase/jp/8/api/java/net/URLClassLoader.html

こんな感じです。

■ test1.jar 内の Test.java を使いたい場合

import test.*;
import java.net.*;
import java.lang.reflect.*;

class Main {
  public static void main(String[] args) throws Exception {
        URLClassLoader ucl = new URLClassLoader(new URL[] { new URL("file:///usr/local/tmp/java/jarLoad/test1.jar") }, null);
        Class c = ucl.loadClass("test.Test");
        Method m = c.getMethod("test");
        System.out.println(m.invoke(c.newInstance()));
  }
}

■ 実行結果

[root@centos62 jarLoad]# java -cp .:test1.jar:test2.jar Main
11111

ちゃんと "33333" 返す Test.java ではなく、"11111" 返す Test.java ( test1.jar ) の test() が呼ばれてますね。ちなみに URLClassLoader の第二引数を指定しないと、おそらく親クラスローダに委譲されるっぽく "33333" 返す Test.java が呼ばれちゃいます。

こっちも verbose:class でロードされたクラスを見てみる。

■ URLClassLoader の第二引数を null に指定する

[root@centos62 jarLoad]# java -cp .:test1.jar:test2.jar -verbose:class Main | grep Test
[Loaded test.Test from file:/usr/local/tmp/java/jarLoad/test1.jar]

■ URLClassLoader の第二引数を指定しない

[root@centos62 jarLoad]# java -cp .:test1.jar:test2.jar -verbose:class Main | grep Test
[Loaded test.Test from file:/usr/local/tmp/java/jarLoad/]


以上です。

[ 環境情報 ]
CentOS 6.2
Java SE 8