ついでにClassCastExceptionも出しておきました。ドッペルゲンガーと紹介されてるやつです。
内部でキャストのテストを行うcastMe()メソッドを持つインタフェースと、その実装クラスを定義します。
/* TestInterface.java */
public interface TestInterface{
public void castMe(TestInterface object);
}
/* TestClass.java */
public class TestClass implements TestInterface{
public void castMe(TestInterface object){
System.out.println(object + "を");
System.out.println(this +
"のcastMe()メソッドでTestClassにキャストします。");
TestClass tc = (TestClass) object;
System.out.println("成功。");
}
}で、これを利用するクラスでは、通常のCLASSPATHベースのローダではなく、URLClassLoaderによってクラスをロードさせます。検索URLとして、カレントディレクトリ下のhoge1ディレクトリとhoge2ディレクトリを検索するローダーを、それぞれ一つずつ、計2つのローダを作ります。これらのローダは、CLASSPATHベースのローダの子となります。
import java.net.URLClassLoader;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
class Test{
static public void main(String[] args){
URL url1 = null;
URL url2 = null;
try{
// クラス検索URLの作成
url1 = new File("hoge1").toURI().toURL();
url2 = new File("hoge2").toURI().toURL();
}catch(MalformedURLException mue){
mue.printStackTrace();
return;
}
// ローダーの作成
ClassLoader cl1 = new URLClassLoader(
new URL[] {url1},
ClassLoader.getSystemClassLoader()
);
ClassLoader cl2 = new URLClassLoader(
new URL[] {url2},
ClassLoader.getSystemClassLoader()
);
TestInterface hoge1 = null;
TestInterface hoge2 = null;
try{
// それぞれのローダーで実装クラスをロード
Class clas1 = cl1.loadClass("TestClass");
hoge1 = (TestInterface) clas1.newInstance();
Class clas2 = cl2.loadClass("TestClass");
hoge2 = (TestInterface) clas2.newInstance();
}catch(ClassNotFoundException e){
e.printStackTrace();
return;
}catch(InstantiationException e){
e.printStackTrace();
return;
}catch(IllegalAccessException e){
e.printStackTrace();
return;
}
// それぞれのローダーで作ったクラス同士で、キャストのテスト
hoge1.castMe(hoge1);
hoge2.castMe(hoge2);
hoge1.castMe(hoge2);
}
}まずは、全部のクラスをカレントに置いて実行してみます。hoge1とhoge2を検索するローダーですが、親にCLASSPATHベースのローダーが居るので、hoge1とhoge2ディレクトリに目的のクラスがなくても無事に検索出来ます。
$ java Test TestClass@50e1fを TestClass@50e1fのcastMe()メソッドでTestClassにキャストします。 成功。 TestClass@be2358を TestClass@be2358のcastMe()メソッドでTestClassにキャストします。 成功。 TestClass@be2358を TestClass@50e1fのcastMe()メソッドでTestClassにキャストします。 成功。
次に、TestClassをhoge1とhoge2の両ディレクトリに移動します。CLASSPATHから見える位置に置くとそちらが優先されるので、見えないようにしておいて実行します。
$ java Test
TestClass@a2961bを
TestClass@a2961bのcastMe()メソッドでTestClassにキャストします。
成功。
TestClass@a3bcc1を
TestClass@a3bcc1のcastMe()メソッドでTestClassにキャストします。
成功。
TestClass@a3bcc1を
TestClass@a2961bのcastMe()メソッドでTestClassにキャストします。
Exception in thread "main" java.lang.ClassCastException: TestClass
at TestClass.castMe(TestClass.java:5)
at Test.main(test.java:48)無事に(?)ドッペルゲンガーが出ました。TestClass→TestClassのキャストですが、ローダーが違うために別物*1扱いされています。
余談。通常のローダーは親を優先する性質があるのでここまで面倒なことをしないとClassCastExceptionを再現できないですが、tomcat等のように子を優先するローダーを使うと、この現象はもっと簡単に起きると思います。上の例で具体的に言うと、仮に子が優先のローダーでこの実験をすると、CLASSPATHからTestClassを除かなくてもhoge1やhoge2に別のTestClass実装を置いただけでこの例外が起こるようになります。
*1:この場合は継承関係がないって意味の別物