ClassLoader-父子类转换

父类=子类能转换的条件为:

  1. 子类的类加载器==父类的类加载器
    or
  2. 父类的类加载器是子类的类加载器的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文件和类加载器唯一确定的。搞清楚最基本的概念非常重要,不要混淆了。

你可能感兴趣的:(ClassLoader-父子类转换)