Java中类加载机制和反射技术

Posted 精诚所至,金石为开

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中类加载机制和反射技术相关的知识,希望对你有一定的参考价值。

       我们知道一个对象在运行时有两种类型,一个是编译类型,一个是运行时类型。在程序运行时,往往是需要发现类和对象的真实的信息的。那么如何获的这种信息呢?

其一,如果我们在编译和运行时都知道类型的具体信息,这时是可以手动将一个对象转换为运行时的类型。

其二,如果我们在编译时无法预知对象和类到底是属于哪些类,那么程序只有依靠运行时的信息来发现对象和类的真实的信息了,这时就必须要用到反射技术。

在谈具体的反射技术之前,我想先回顾下,有关类的加载的一些基本的性质和原理,以方便我们更好地理解,反射的作用和特点。而实际上,一个类如果可以被执行,那么对于JVM来说,它的执行流程为:类的加载、连接、初始化。通过这种方式,才可以获取到一个类的类对象,即java.lang.Class对象,并在此基础上获取到该类的成员变量,方法和构造器等内在的东东。

   那么,什么是类的加载呢?类的加载就是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序使用任何类时,系统都会为之建立一个java.lang.Class对象。

同时,这个java.lang.Class对象又有什么特点呢?

1、Class是一个类,一个描述类的类(也就是描述类本身),封装了描述方法的Method,描述字段的Filed,描述构造器的Constructor等属性
2、对象照镜子后(反射)可以得到的信息:某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。
3、对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
4、Class 对象只能由系统建立对象
5、一个类在 JVM 中只会有一个Class实例

同时,我们了解下一个基本的类加载器的结构:

类的连接:连接阶段负责把类的二进制数据合并到JRE中,分为三个步骤:

第一,验证,检验被加载的类是否有正确的内部结构;第二,准备,负责为类的类变量分配内存,并设置默认初始值。第三,解析将类的二进制数据中的符号引用替换成直接引用。

 

类的初始化:主要对类变量进行初始化。

 

这样,我们清楚地认识到了一个类的生命周期变化,那么这些也为我们的反射机制带来了铺垫,通过反射要获取一个类的class对象,有三种方式:

       (1)使用Class类的forName(String clazzName)静态方法 

       (2)调用某个类的class属性获取该类对应的Class对象

       (3)调用某个类的getClass()方法

       通过这三种手段就可以获取到了一个类的Class对象,这样就可以动态获取该对象的实际的信息了。下面通过一个具体的例子说明下,如果通过反射机制,创建一个类的对象,如何通过获取的对象再进一步获取该对象的属性和方法,以及动态为该对象注入新的参数值。

 测试实体类:

package reflection;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description
 * @date 2017/10/19
 */
public class Person {
    public String name;
    private int age;

    public Person(){
        System.out.println("无参构造器");
    }
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name=\'" + name + \'\\\'\' +
                ", age=" + age +
                \'}\';
    }

    private void privateMethod(){
        System.out.println("这是一个私有的方法!");
    }

    public void setName(String name,int age){
        System.out.println("getName is:"+name);
        System.out.println("getAge is:"+age);
    }

}

通过反射获取类:

package reflection;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通过反射获取类测试
 * @date 2017/10/19
 */
public class GetClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        //Class是一个描述类的类、对每个类而言只有一个不变的Class与其对应、JRE中只有唯一一个
        Class clazz = null;

        //1 直接通过类名.class方式得到
        clazz = Person.class;
        //输出:reflection.Person
        System.out.println("直接通过类名.class得到:"+clazz);

        //2 通过对象的getClass()方法获取
        Object obj = new Person();
        clazz = obj.getClass();
        //输出:reflection.Person
        System.out.println("通过getClass(): " + clazz);

        //3 通过全类名获取,用的比较多,但可能抛出ClassNotFoundException异常
        clazz = Class.forName("reflection.Person");
        //输出:reflection.Person
        System.out.println("通过全类名获取: " + clazz);
    }
}

获取类的实例:

package reflection;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通过反射获取类的实例测试
 * @date 2017/10/19
 */
public class GetInstanceTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = Class.forName("reflection.Person");
        //使用Class类的newInstance()方法创建类的一个对象
        //实际调用的类的那个 无参数的构造器
        //一般的,一个类若声明了带参数的构造器,也要声明一个无参数的构造器
        Object obj = clazz.newInstance();
        System.out.println(obj.toString());
    }
}

获取类所定义的方法:

package reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通过反射获取类所定义的方法的测试
 * @date 2017/10/19
 */
public class GetMethodTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //获取clazz
        Class clazz = Class.forName("reflection.Person");

        //1、得到clazz 对应的类中有哪些方法,不能获取private方法
        Method[] methods = clazz.getMethods();
        System.out.println("getMethods:");
//遍历方法集合 获取对应的方法名称
for (Method method : methods) { System.out.print(method.getName() + ", "); } System.out.println(); //2、获取所有的方法(包括private方法) Method[] methods2 = clazz.getDeclaredMethods(); System.out.println("getDeclaredMethods:"); for (Method method : methods2) { System.out.print(method.getName() + ", "); } System.out.println(); //3、获取指定的方法 Method method = clazz.getDeclaredMethod("setName", String.class); System.out.println("method : " + method); Method method2 = clazz.getDeclaredMethod("setName", String.class, int.class);//第一个参数是方法名,后面的是方法里的参数 System.out.println("method2: " + method2); //4、执行方法 Object obj = clazz.newInstance();
//此处注意下该invoke方法,参数为:执行该方法的对象,为该方法传入的参数列表 method2.invoke(obj,
"jaycee", 23); } }

获取Fileds:

package reflection;

import java.lang.reflect.Field;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通过反射获取field测试
 * @date 2017/10/19
 */
public class GetFieldTest {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class clazz = Class.forName("reflection.Person");

        //1、获取字段
        //1.1 获取Field的数组,私有字段也能获取
        Field[] fields = clazz.getDeclaredFields();
        for (Field field: fields) {
            System.out.print(field.getName() + ", ");
        }

        //1.2 获取指定名字的Field
        Field field = clazz.getDeclaredField("name");
        System.out.println("\\n获取指定Field名=: " + field.getName());

        Person person = new Person("jaycee", 23);
        //2、获取指定对象的Field的值
        Object val = field.get(person);
        System.out.println("获取指定对象字段\'name\'的Field的值=:" + val);

        //3、设置指定对象的Field的值
        field.set(person, "admin");
        System.out.println("设置指定对象字段\'name\'的Field的值=: " + person.name);

        //4、若该字段是私有的,需要调用setAccessible(true)方法
        Field field2 = clazz.getDeclaredField("age");
        field2.setAccessible(true);
        System.out.println("获取指定私有字段名=: " + field2.getName());
    }
}

获取父类定义的信息:

申明父类:

package reflection.invoke;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 父类
 * @date 2017/10/19
 */
public class Father {
    /**
     * 父类无参构造函数
     */
    public Father(){

    }
    /**
     * 父类私有方法1
     */
    private String method(){
        return "Father\'s private method";
    }
}

申明子类:

package reflection.invoke;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 儿子类
 * @date 2017/10/19
 */
public class Son extends Father{
    /**
     * @author jiaqing.xu@hand-china.com
     * @date 2017/10/19 10:19
     * @param
     * @return
     * @description 儿子私有方法
     */
    private void method1(Integer age){
        System.out.println("son\'s age=:" +age);
    }
}

获取父类中定义的方法:

package reflection.invoke;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 获取父类中定义的私有的方法
 * @date 2017/10/19
 */
public class GetSuperClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        String className = "reflection.invoke.Son";
        Class clazz = Class.forName(className);
        //得到父类的Class
        Class superClazz = clazz.getSuperclass();
        //输出:class reflection.invoke.Father
        System.out.println(superClazz);
    }
}

 总结下反射的优点和缺点:

1 优点:
2 (1)提高程序的灵活性和拓展性,能够在运行时动态地获取类的实例。
3 (2)和java的动态编译相结合,可以提高更强大的功能。
4 (3)提前无需硬编码,便可以通过类名获取对应类的实例,进而操作该实例。
5 
6 缺点:
7 (1)性能较低,反射是一种接入式的操作需要找到对应的字段和方法,比起直接的代码赋值,要慢的多。
8 (2)使用反射应该在一个相对安全的环境下进行。
9 (3)使用时会破坏类的封装性,破坏OOP的整体设计思想。

 

以上是关于Java中类加载机制和反射技术的主要内容,如果未能解决你的问题,请参考以下文章

java反射和动态代理有啥关系

Java中类加载器和双亲委派机制

java 中类加载器

6.虚拟机类加载机制

反射Practices

java中类的加载顺序介绍(ClassLoader)