服务热线: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对象来进行动态装载类的。

扫描关注微信公众号