类加载机制和反射的理解

      背景知识了解: 一个类加载离不开Jvm,Jvm是什么?Java Virtual Machine(Java虚拟机)的缩写。Jvm执行字节码(.class文件)时,将其翻译成各个平台都能运行的机器指令。因此,Java能一次编译,到处运行(各个平台)。

       运行一个java程序,即可启动一个jvm进程。同一个jvm进程里的所有线程,变量共享。不同的Jvm进程 是独立的,一个Jvm运行结束后,该程序里运行的变量在内存不占空间了。因此他们互相不干涉。

      进入主题: 在一个Jvm想用一个类到把这个类加载进入内存这个过程中发生什么事情呢?

      经过三步骤: 1. 类加载 2.类连接 3.类初始化

      首先,类加载就是把Class文件放入内存里,创建一个Class对象。Ps:要使用类,就必须有“对象”。(所以提交代码时候,.java文件没提交没关系,只要.class文件提交了就万事不怕啦)

     接着,类连接,在干三件事。检查语法是不是有问题,和其他类是不是协调。给静态成员赋一个初始值。最后就是解析,将符号引用改成直接引用。(最后一步,开始不太理解,可参考例子:int a = 1;解析就是把a直接改成1   

    最后,类初始化主要对类变量进行初始化。以下两步:1、声明类变量并指定初始值 2、静态初始化块时给类变量一个初始值。(先直接父类,间接父类,再自己类初始化)

类啥时候初始化呢?

举例:

创建类的实例。

调用类方法。

访问类变量或为静态变量赋值。

反射的方法强行创建类或者接口对应的java.lang.Class对象。

初始化某父类的子类。(父类会被先初始化)

java.exe直接运行某主类。先初始化该主类。

小注意:程序访问静态变量也可能不会初始化类(“宏变量”使用:final初始化 类变量值,该值编译时就能确定)

package Test01;
class Test{
    static{
        System.out.println("静态初始化块.......");
    }
    final static int i=0;
}
public class CompileConstantTest {

    
    public static void main(String[] args) {
        System.out.println(Test.i);
    }
}

看结果发现类并没执行初始化块,所以类没初始化。

理解 Jvm借助类加载器这个工具把.class文件加载进内存。

类加载器

根加载器:加载系统核心类,。不是java.lang.ClassLoader的子类。

扩展加载器 %JAVA_HOME%/jre/lib/ext(我的机子是 D:\jdk\jre\lib\ext)在该目录下把自己写的类打包进去,可扩展核心类以外的新功能。(系统类加载器的父加载器)

系统类加载器:静态方法getSystemClassLoader(),最常用。加载来自CLASSPATH环境变量的类及jar包,java命令的-classpath属性和java.class.path系统属性(这就是为什么运行Java要配置path,classpath环境变量的原因了,配置详细可见   http://www.cnblogs.com/Fskjb/archive/2011/01/06/1927581.html)

下图来自  http://www.cnblogs.com/wangrongchen/p/9242789.html

 

    反射

为什么需要反射?

背景知识: Java引用变量有两种类型,一种编译时类型,一种运行时类型。编译时类型由声明变量使用的类型决定。运行时类型由实际赋给他的变量决定。两个类型不一致的话,就可能出现“多态”的情况。

package Test01;
class Base{
	final static int book=6;
	public void base(){
		System.out.println("老爸有的函数.......");
	} 
	public void test() {
		System.out.println("普通有的函数.......");
	}
}
public class SubClass extends Base{
	final static String book="哈哈,我修改了book";
	public void test() {
		System.out.println("子类重写的函数.......");
	}
	public void sub(){
		System.out.println("子类才有的函数.......");
	}
	public static void main(String[] args) {
		
		Base base =new Base();
		base.base();
		base.test();
		System.out.println("base里的book是"+base.book); 
		SubClass sub =new SubClass();
		sub.base();
		sub.test();
		System.out.println("sub里的book是"+sub.book);
		//编译和引用类型不一样的
		Base b =new SubClass();
		b.base();
		b.test();
		System.out.println("b里的book是"+b.book);
		//b编译时是Base类型,没提供sub()方法
	/*	b.sub();*/
		
	}
}

  以上代码说明:

1.对象的方法具有多态性。最后一行注释的代码不能通过编译,虽然b的引用变量确实包含了sub方法,但是编译时它仍是Base类型,不能执行 SubClass里的方法(提示:可使用反射来执行该方法)。

2.对象的实例变量不具有多态性。b里的book不是输出子类的book,而是父类的。

运行结果如下:

       总结下可能出现的问题:程序运行会出现编译和运行的类型不一致的情况,比如Person p =new Student(); 该行代码产生一个p变量,编译时 是Person类型,运行时是Student类型。更不好的例子是程序如果接收外部的一个对象,这个对象编译时是Object,但是运行时却调用对象运行类型时的方法。

解决 程序需要知道 运行时对象和类的信息 情况  这个问题的办法有:

1.假设 运行和编译就完全已经知道对象和类的信息,则强制类型转换,将编译时类型强转为运行时类型。强转时注意:基本数据类型只能在数值类型之间转换(整型,字符型,浮点型),数值型不能喝布尔型转。存在继承关系才父子类之间才能进行强转,否则会CastClassException。因此,在引用强转时候加上instanceof(instanceof用法不清楚的可以搜一下)判断下再转,能增加代码的健壮性。

2假设 只能运行 才能发现对象和类的信息,就得靠 反射解决问题。

下面进入重点啦:

如何获得Class对象呢?

使用Class类的静态方法foreName(String clazzName)方法传入的字符串必须是类的全限定名,包括包名。

1   Class a= Class.forName("Test01.Classloader");
2    System.out.println(a);

类的class属性。Person.class.

1  Class a= Base.class;

调用某对象的getClass()方法。该方法是java.lang.object类里的方法,所以所有的对象都能使用。

Base b =new Base();
Class a= b.getClass();

对比下,前两种方法都是根据类取得类指定Class对象,但是第二个更好,因为不需要调用方法,而且代码更安全,编译阶段就检查了要访问的该Class对象是否存在。

获得Class对象了还没结束,还需要调用里面的方法来获得Class对象和该类的详细信息。

  获得Class对象里的构造器,Constructor (在获得类里必须有对应的构造器)

   Class a= Class.forName("Test01.Classloader");
   //无参数
   Constructor  c = a.getConstructor(null);
   //有参数
   Constructor  m = a.getConstructor(String.class,int.class); //数据类型在前,class后面,具体看构造参数
   System.out.println(m);

     获得Class对象里的方法,Method

Class a= Class.forName("Test01.Classloader");
Method  m = a.getMethod("test", String.class); //第一个是方法名字,后面的是参数类型
   System.out.println(m);

      获得Class对象里的实例变量,Field(注:如果同一个包下的两个类,一个被访问类里的成员变量省略修饰符,会报找不到成员变量的错)

 Class a= Class.forName("Test01.Classloader");
  
   Field  m = a.getField("name"); //第一个是方法名字,后面的是参数的类型
   System.out.println(m);

   创建Class对象的实例,并调用里面的方法举例

package Test01; 
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
public class SubClass{
 
	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, NoSuchFieldException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Class a= Class.forName("Test01.Classloader");
  
       Object object    = a.newInstance(); //一个实例
       Method test =a.getMethod("test", String.class);
       test.invoke(object, "zyz");
 	}
}

  修改公共的成员变量举例

  Class a= Class.forName("Test01.Classloader");
	   Object object    = a.newInstance(); //一个实例
	   Field f =a.getField("name");
	   f.set(object, "zyz");

  

猜你喜欢

转载自www.cnblogs.com/yizhizhangBlog/p/9248265.html