一般的class都是由系统类加载器、或者其父加载器或者bootstartup加载器加载的,但在应用中可以指定类的加载器,然后将生成的class对象造型为具体的类。
public class LoadClass {
public static void main(String args[]) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException{
URL url=new URL("file:/G:/test/");
URLClassLoader loader=new URLClassLoader(new URL[]{url});
Class c=loader.loadClass("LoadModel");
Object o=c.newInstance();
URL url=new URL("file:/G:/test/");
URLClassLoader loader=new URLClassLoader(new URL[]{url});
Class c=loader.loadClass("LoadModel");
Object o=c.newInstance();
Comparable comparable=(Comparable)o;
System.out.println("result:"+comparable.compareTo(""));
}
}
}
}
class LoadModel implements Comparable{
public int compareTo(Object o){
return 33;
}
}
以上的LoadModel类的class文件放置在G:/test/目录中(必须把LoadModel.java编译成class文件),而LoadClass.java放置在其他文件夹(避免通一个classpath,否则使用loader动态装载就没效用了).以上代码执行时可以获取结果。
注意:不同的加载器即使加载的是同一个类,在jvm看来他们也是不同的类,不能进行转换。例如上面例子使用一个新的URLClassLoader动态加载了一个LoadModel实例(先使用newInstance()方法生成一个Object对象,其实该对象的类型是LoadModel),假设LoadModel在当前的类路径,即在G:/test/下有LoadModel.java文件,当使用LoadModel m=(LoadModel)o 进行强制转换时会出现ClassCastException异常,原因就是这两个LoadModel类型不在同一个ClassLoader中。要想使程序能运行,则须设定LoadModel基础一个当前类路径上的类或实现当前类路径上的一个接口,然后转换类型的时候把该类强制转换为其父类或其实现的接口就可以了,如上例(假如继承的类或实现的接口不是核心API里面的,那还必须在该类的classpath中定义该类的父类或其实现的接口),之所以可以这样是因为该类实现的接口或者继承的父类都是由同一个类加载器加载的(系统类加载器)。
以上所说的可以概况为,两个类进行转换的的必要条件是他们都是由同一个类加载器加载,当我们的目标类实现了java核心API中的接口或者继承了核心API的类时,因为继承的接口和类只能在系统类加载器或扩展加载器又或者是bootstartup类加载器之一加载。我们的测试类肯定是由这三者之一加载了,根据类加载的全盘负责原则,测试类中的所有类也由测试类的加载器加载(除非显式的使用类加载器加载),这样可以确定了在测试类中出现的目标类接口(或者父类)是由系统加载器加载;另外我们测试类中显式的使用类加载器加载的目标类是由我们自定义的类加载器加载,而在目标类出现的目标类接口(或父类)也由我们自定义的类加载器加载,但根据类加载原则,自定义加载器会委托父加载器加载目标接口,这样,目标接口(或父类)就变成了使用系统类加载器加载了,这样测试类的目标接口(父类)与目标类所在文件的目标接口(父类)都由系统类加载器加载,从而他们也就可以相互转换了。
另外必须把LoadModel.java编译成class文件的原因是,javac命令只能编译当前类路径的java文件,而LoadModel.java并不在当前classpath中。ClassLoader中的loadClass方法并不能编译java文件为class文件,它只是将.class文件载入内存而已。