服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java程序类的加载及其反射机制全面介绍

java中类文件加载是动态的。jvm指令是被封装在了. class文件里面,而.class文件的加载过程是动态的,也就是说当我们用到的时候才会去加载,如果不用的话,就不会去加载我们的类。这里所说的用到包括两种方式,第一种就是new一个对象的时候(这个时候要特别注意,当设计到多态的时候,就会有一点点变化,这时候编译器会做一些优化,这样以来当加载的时候会提前加载设计到多态的类,关于这一点下面有个例子(example 1)来说明。另一种就是当一个类的静态代码被调用的时候。



java 代码//example 1 // zoo.java abstract class animal { animal(){ system.out.println("animal constructor"); } } class tiger extends animal { tiger(){ system.out.println("tig constructor "); } } class dog extends animal { dog(){ system.out.println("dog constructor "); } } public class zoo { private animal am; //example 1.1 //private dog am; example 1.2 private tiger tiger; zoo(){ tiger = new tiger(); am = new dog(); } public static void main(string [] args){ system.out.println("new zoo before"); zoo z = new zoo(); system.out.println("new zoo after "); } }




  当我们注释掉example.1.1行时,运行example1.2行,结果如下:

  example 1.2

  分析以上两图的运行结果我们可以看出:当我们将子类对象赋值给父类时,编译器会做一点优化,于是加载器在还没有new 子类对象的时候已经加载了父类以及子类(example1.1结果),当不存在多态的时候,我们可以看到是当要new dog()的时候才会加载dog以及父类。无论何种方式,在new之前,类确实已经加载到了内存中。

  java为我们提供了两种动态机制。第一种是隐式机制。其实new一个对象和调用类的静态方法时,就是隐式机制在工作。第二种是显示机制。显示的机制又有两种策略(第一种是用java.lang.class的forname(string str)方法,第二种是用java.lang.classloader的loadclass())。

  第一种:利用forname方法

  当我们查api文档就会发现forname方法有两种形式。分别如下:


public static class<?> forname(string classname) throws classnotfoundexception public static class<?> forname(string name, boolean initialize, classloader loader) throws classnotfoundexception




  先来说说第二种方法:第二个方法值得注意的就是第二个参数boolean initialize,如果我们把这个参数设置为false,那么当我们加载完类后就不会执行静态代码和静态的初始化动作。只有当我们new一个对象的时候才会初始化。而第三个参数是用来指明类的加载器的。

  如果查看java.lang.class类的源代码,上述两种方法最终都会调用class类中的私有的native方法forname0(),此方法的声明如下:


private static native class forname0(string name, boolean init , classloader loader) throws classnotfoundexception;




  所以当我们调用class.forname(name )时,其实是在方法内部调用了:


forname0(name, true, classloader.getcallerclassloader());



  当我们调用class.forname(name, initialize, loader )的时候,实际上此方法内部调用了:


forname0(name, initialize, loader);




  下面看一个例子,如果方法中第二个参数为false的情况:


java 代码//example 2.1 // zoo.java abstract class animal { static { system.out.println("animal static code block "); } animal(){ system.out.println("animal constructor"); } } class tiger extends animal { tiger(){ system.out.println("tig constructor "); } } class dog extends animal { dog(){ system.out.println("dog constructor "); } } public class zoo { public static void main(string [] args)throws exception { system.out.println("new zoo before"); zoo z = new zoo(); class c = class.forname("dog",false,z.getclass().getclassloader()); system.out.println("initilize before "); animal dog = (animal)c.newinstance(); system.out.println("new zoo after "); } }




  类加载完成后并没有立即执行静态初始化代码,而是到了实例化的时候才进行了静态初始化。有时候我们会说静态代码是在类第一次被加载时执行的,并且只执行一次。其实这是对与new一个对象,第一次访问类的静态代码以及第二个参数为true时而言的,对于动态的加载来说,如果forname方法的第二个参数设置为false,那么就是在实例化的时候才会执行静态初始化。当然默认情况下第二个参数是true.

  第二种方法:利用class对象获取的classloader装载。

  下面是一个简单的例子:


java 代码//example 2.2 //zoo.java abstract class animal { static { system.out.println("animal static code block "); } animal(){ system.out.println("animal constructor"); } } class tiger extends animal { tiger(){ system.out.println("tig constructor "); } } class dog extends animal { dog(){ system.out.println("dog constructor "); } } public class zoo { public static void main(string [] args)throws exception { class c = zoo.class; classloader loader = c.getclassloader(); system.out.println("loader before"); class dog = loader.loadclass("dog"); system.out.println("instance before "); animal an = (animal)dog.newinstance(); } }




  loader完成以后并没有立即进行静态代码的执行。只有当newinstance()的时候才执行静态初始化,这和把public static class forname(string name, boolean initialize, classloader loader)的第二个参数指定为false的情况完全一样。其实每当我们写完一个编译单元以后就会得到一个.calss文件,这个文件中就包含了该类的class对象。jvm就是利用这个class对象来进行动态装载类的。


扫描关注微信公众号