ついでに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:この場合は継承関係がないって意味の別物