父类=子类
能转换的条件为:
- 子类的类加载器==父类的类加载器
or - 父类的类加载器是子类的类加载器的parent(不要求直接parent)
类由class文件+ClassLoader唯一确定
首先,运行时刻** 类是由class文件 + 类加载器唯一确定的 **。即同一个class文件如果加载器不一样,运行时也是两个不同的类,测试代码如下:
public static void main(String[] args) throws Exception {
URL[] urls = new URL[] {new URL("file:/Temp/")};
ClassLoader cl1 = new URLClassLoader(urls, null);
ClassLoader cl2 = new URLClassLoader(urls, null);
Class clz1 = cl1.loadClass("Test");
Class clz2 = cl2.loadClass("Test");
System.out.println(clz2.isAssignableFrom(clz1));
}
测试结果是:false
问题
由于类是由class文件 + 类加载器唯一确定的, 所以下面这段代码是会抛cast异常的:
public static void main(String[] args) throws Exception {
URL[] urls = new URL[] {new URL("file:/Temp/")};
ClassLoader cl = new URLClassLoader(urls, null);
Class clz = cl.loadClass("Test");
System.out.println("1:" + Test.class.getClassLoader());
System.out.println("2:" + clz.getClassLoader());
Test test = (Test)clz.newInstance();
}
输出如下:
1:sun.misc.Launcher$AppClassLoader@18b4aac2
2:java.net.URLClassLoader@60e53b93
Exception in thread "main" java.lang.ClassCastException: Test cannot be cast to Test
at ClassLoaderTest3.main(ClassLoaderTest3.java:15)
由于Test是由AppClassLoader加载的,但是clz是由URLClassLoader加载的,从而会出现ClassCastException。
我们把上面代码稍微改一下:TestInterface作为接口,有一个实现类:TestImpl。是否能进行向上转型呢?
测试代码如下:
public static void main(String[] args) throws Exception {
URL[] urls = new URL[] {new URL("file:/Temp/")};
ClassLoader cl = new URLClassLoader(urls);
Class clz = cl.loadClass("TestImpl");
System.out.println("1:" + TestInterface.class.getClassLoader());
System.out.println("2:" + clz.getClassLoader());
TestInterface test = (TestInterface)clz.newInstance();
}
输出如下:
1:sun.misc.Launcher$AppClassLoader@18b4aac2
2:java.net.URLClassLoader@60e53b93
Process finished with exit code 0
TestInterface是由AppClassLoader加载的,clz是由URLClassLoader加载的,但,居然能够正常的向上转型!我期待的异常居然没抛!
分析
细心的人可能已经发现了,这两个比对的测试样例生成ClassLoader的地方不一样。
第一个例子:
ClassLoader cl = new URLClassLoader(urls, null);
第二个例子:
ClassLoader cl = new URLClassLoader(urls);
这里URLClassLoader的构造方法如下:
public URLClassLoader(URL[] urls, ClassLoader parent)
如果不传第二个参数,默认使用上下文类加载器作为parent,即AppClassLoader作为parent加载器。
双亲委派协议这里就不具体说了,太多相关文章了。
接下来,我们分析一下第二个测试用例:
public class TestImpl implements TestInterface
在使用URLClassLoader加载TestImpl时,由于TestImpl实现了TestInterface,会先去加载TestInterface,在加载TestInterface时,由于双亲委派协议,TestInterface加载时,会委托使用URLClassLoader的父加载器AppClassLoader进行加载,恰好AppClassLoader可以进行加载,从而TestImpl是通过AppClassLoader加载的,TestInterface是通过URLClassLoader加载的。
最后,下面代码的左边TestInterface和clz的接口TestInterface都是由URLClassLoader加载的,从而没有转型问题。
TestInterface test = (TestInterface)clz.newInstance();
总结一下,父子关系是和类加载器没有关系的,类型是由class文件和类加载器唯一确定的。搞清楚最基本的概念非常重要,不要混淆了。