java 中的反射机制

来源:互联网 发布:msp430中文数据手册 编辑:程序博客网 时间:2024/05/16 07:20

(一)、Class类

① 在面向对象的世界里,万事万物皆对象(java语言中,静态的成员、普通的数据类型是不是对象呢?类是谁的对象呢?)

类是对象,类是 java.lang.Class类的实例对象

 

② 这个对象如何表示呢?

public static void main(String[] args) {

//Foo的实例对象如何表示

Foo foo1 = new Foo();//foo1就表示出来了

//Foo这个类也是一个实例对象,Class类的实例对象,如何表示呢?

//Class这个类是final修饰的,构造方法私有,有JVM虚拟机来创建该类的对象

//任何一个类都是Class类的实例对象,而这个实例对象由三种表示方式

//第一种表示方法---->实际在告诉我们任何一个类都有一个隐含的静态成员变量class

Class c1 = Foo.class;

//第二种表达方式,已知该类的对象通过getClass()方法

Class c2 = foo1.getClass();

/*官网c1,c2表示了Foo类的类类型(class type)---->表示Foo这个类本身就是一个对象,是Class类的对象

*万事万物皆对象;

*每一个类也是对象,是Class类的实例对象;

*这个对象我们称为该类的类类型;

*/

//c1 or c2都代表了Foo类的类类型,但一个类只可能是Class类的一个实例对象

System.out.println(c1 == c2);//true


//第三种表达方式Class c3 = null;try {c3 = Class.forName("com.xupt.reflect.Foo");} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println(c2 == c3);//true//我们完全可以通过类的类类型,创建该类的对象 --->通过 c1 or c2 or c3创建Foo实例try {Foo f1 = (Foo) c1.newInstance();//创建该类的类对象,要强制类型转换(需要提供一个无参数的构造方法)f1.print();} catch (InstantiationException | IllegalAccessException e) {e.printStackTrace();}}


③ Class类动态加载的认识:

Class.forName(“类的全称”);

不仅表示了类的类类型,还代表了动态加载类

要区分编译、运行两个状态

编译时刻加载类是静态加载、运行时刻加载类是动态加载类


//new 创建对象是静态加载类,在编译时刻就需要加载所有的可能用到的类//通过动态加载类,可以解决该问题public static void main(String[] args) {try{//动态加载类,在运行时刻加载Class c = Class.forName(args[0]);//通过类类型,创建该类的类对象(接口实现原理)OfficeAble oa = (OfficeAble)c.newInstance();oa.start();}catch(Exception e){e.printStackTrace();}}



(二)Class类的引用 

   jvm为每个加载的类类型(译者:包括类和接口)都创建一个java.lang.Class的实例。而jvm必须以某种方式Class的这个实例和存储在方法区中的类型数据联系起来 

   (1)你可以通过Class类的一个静态方法得到这个实例的引用// A method declared in class java.lang.Class: public static Class forName(String className); 假如你调用forName("java.lang.Object"),你会得到与java.lang.Object对应类的实例对象。你甚至可以通过这个函数 得到任何包中的任何已加载的类引用,只要这个类能够被加载到当前的名字空间。如果jvm不能把类加载到当前名字空间, forName就会抛出ClassNotFoundException。

   (2)也可以通过任一对象的getClass()函数得到类对象的引用,getClass被声明在Object类中: // A method declared in class java.lang.Object: public final Class getClass(); 例如,假如你有一个java.lang.Integer的对象引用,可以激活getClass()得到对应的类引用。

 

通过类对象的引用,你可以在运行中获得相应类存储在方法区中的类型信息,下面是一些Class类提供的方法: 

// Some of the methods declared in class java.lang.Class: 

public String getName(); 

public Class getSuperClass(); 

public boolean isInterface(); 

public Class[] getInterfaces(); 

public ClassLoader getClassLoader(); 


   这些方法仅能返回已加载类的信息。getName()返回类的完整名,getSuperClass()返回父类的类对象,isInterface()判断是否是接口。getInterfaces()返回一组类对象,每个类对象对应一个直接父接口。如果没有,则返回一个长度为零的数组。 getClassLoader()返回类加载器的引用,如果是由启动类加载器加载的则返回null。所有的这些信息都直接从方法区中获得。 


方法表 为了提高访问效率,必须仔细的设计存储在方法区中的数据信息结构。除了以上讨论的结构,jvm的实现者还可以添加一些其他的数据结构,如方法表。jvm对每个加载的非虚拟类的类型信息中都添加了一个方法表,方法表是一组对类实例方法的直接引用(包括从父类继承的方法)。jvm可以通过方法表快速激活实例方法。(译者:正像java宣称没有指针了,其实java里全是指针。更安全只是加了更完备的检查机制,但这都是以牺牲效率为代价的。) 


举一个例子:class Animal {     private int speed = 5;     void run() { //书写代码    } } public class Test {     public static void main(String[] args) {         Animal animal = new Animal();         animal.run();     } } 


下面我们来描述一个 main()方法的第一条指令的字节码是如何执行的。

1)为了运行这个程序,你以某种方式把“Test"传给了JVM。有了这个名字,jvm找到了这个类文件(Test.class 字节码文件)并读入,它从类文件提取了类型信息并放在了方法区中,通过解析存在方法区中的字节码,jvm激活了main()方法,在执行时,JVM 持有了一个指向当前类(Test)常量池的指针。 

注意 JVM 在还没有加载Animal类的时候就已经开始执行了。正像大多数的JVM 一样,不会等所有类都加载了以后才开始执行,它只会在需要的时候才加载。 

2)main()的第一条指令告知JVM 在常量池第一项的类分配足够的内存。jvm使用指向Test常量池的指针找到第一项,发现是一个对Animal类的符号引用,然后它就检查方法区看Animal是否已经被加载了。 

这个符号引用仅仅是类Animal的完整有效名”animal“。这里我们看到为了JVM 能尽快从一个名称找到一个类,一个良好的数据结构是多么重要。这里JVM 的实现者可以采用各种方法,如hash表,查找树等等。同样的算法可以用于Class类的forName()的实现。 

3)当jvm发现还没有加载过一个称为"Animal"的类,它就开始查找并加载类文件"Animal.class"。它从类文件(Animal.class文件)中抽取类型信息并放在了方法区中。 JVM 于是以一个直接指向方法区animal类的指针替换了常量池第一项的符号引用。

4)jvm终于开始为新的Animal对象分配空间了。这次,jvm仍然需要方法区中的信息。它使用指向animal数据的指针(刚才指向Test常量池第一项的指针)找到一个animal对象究竟需要多少空间。jvm总能够从存储在方法区中的类型信息知道某类型对象需要的空间。

一旦jvm知道了一个Animal对象所要的空间,它就在堆上分配这个空间并把这个实例的变量speed初始化为缺省值0。假如Animal的父对象也有实例变量,则也会初始化。 

5)当把新生成的Animal对象的引用压到栈中,第一条指令也结束了。下面的指令利用这个引用激活java代码把speed变量设为初始值 5。另外一条指令会用这个引用激活Animal对象的run()方法。







                                             
0 0