li573925122

前言:

在学习JAVA的反射之前我觉得很有必要花几分钟时间了解一下JVM的内存结构。如果一开始就说什么是反射,以及反射的API。这样做是很难真正的理解反射的。

JVM是JAVA跨平台的核心,其结构为上图所示。我们注意到其运行时数据区也就是我们常说的内存结构包括堆区,方法区,栈区等。那么我们JAVA程序在运行的时候,运行时数据区每个区域存储的哪些数据呢。

程序计数器:简单将程序计数器就是用来指示需要执行哪条指令。由此看出它是线程私有的,因为我们知道多线程的本质是线程抢占CPU时间片的过程,所以我们线程中指令不断的终止和运行,所以要回到运行状态必须要程序计数器记录下应该执行的指令编号。(因为每天指令的大小是固定的所以不会存在内存泄漏的情况)

JAVA栈:JAVA栈也称为JAVA执行方法的内存模型,存储了方法中声明的局部变量包括声明的局部变量形参,运行时常量池引用以及返回地址。JAVA的运行其实就是一个方法栈帧的压栈与弹栈的过程。

堆区:Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)。只不过和C语言中的不同,在Java中,程序员基本不用去关心空间释放的问题,Java的垃圾回收机制会自动进行处理。因此这部分空间也是Java垃圾收集器管理的主要区域。另外,堆是被所有线程共享的,在JVM中只有一个堆。

方法区:方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

类加载的过程其实就是JVM将外部的静态class字节码加载到JVM方法区形成动态数据并在堆中形成一个java.lang.Class对象。这个对象相当于一面镜子与方法区中真正对象对应,这个Class对象就是反射的核心。

了解Class

既然Class对象是反射的核心,那么什么是Class呢?我们来看一下JDK7中是怎么定义的:

 * Instances of the class {@code Class} represent classes and
 * interfaces in a running Java application.  An enum is a kind of
 * class and an annotation is a kind of interface.  Every array also
 * belongs to a class that is reflected as a {@code Class} object
 * that is shared by all arrays with the same element type and number
 * of dimensions.  The primitive Java types ({@code boolean},
 * {@code byte}, {@code char}, {@code short},
 * {@code int}, {@code long}, {@code float}, and
 * {@code double}), and the keyword {@code void} are also
 * represented as {@code Class} objects.
 *
 * <p> {@code Class} has no public constructor. Instead {@code Class}
 * objects are constructed automatically by the Java Virtual Machine as classes
 * are loaded and by calls to the {@code defineClass} method in the class
 * loader.

大概意思为:Class的实例代表正在运行JAVA程序中的对象和接口。枚举类型是一种对象而注解是一种接口。每个数组是被映射为Class的一个类,而具有相同数据类型和维数的数组都共享该Class类。基本数据类型依然是Class对象。Class没有公共的构造方法。所有的Class对象都是在JVM被加载的时候自动调用defineClass创建。

反射作用

  1. 可以通过反射得到类成员变量,成员方法以及构造方法并可以操作他们,即使是私有的也可以访问并操作。
  2. 提高代码灵活性

反射示例

首先创建一个测试Student类:

public class Student {
    private int age;
    private String name;
    private double score;    //三个私有属性
    
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getScore() {
        return score;
    }
    public void setScore(double score) {
        this.score = score;
    }
    
    public Student(){//公共不带参构造
    }
    
    private Student(int age,String name,double score){//私有带参构造
        this.age = age;
        this.name = name;
        this.score = score;
    }
}
  1. 三种方式获取Class对象:
    Class clazz1 = Class.forName("reflection.Student");
            Class clazz2 = Student.class;
            Student student = new Student();
            Class clazz3 = student.getClass();
            
            System.out.println(clazz1==clazz2);
            System.out.println(clazz1==clazz3);

   输出结果为:ture/ture 根据传递性可知这三个对象是同一个

   2.通过反射得到实例对象  

      Class clazz1 = Class.forName("reflection.Student");
        Object obj = clazz1.newInstance();//调用了无参构造函数

  3.通过反射得到类私有变量并赋值

        Class clazz1 = Class.forName("reflection.Student");

        Object o = clazz1.newInstance();
        Field files1 = clazz1.getDeclaredField("age");
        files1.setAccessible(true); //打破私有访问属性
        files1.set(o, 12);
        System.out.println(files1.get(o));

  4.通过反射执行私有构造函数

    Constructor a = clazz1.getDeclaredConstructor(int.class,String.class,double.class);
        a.setAccessible(true);
        Student stu = (Student)a.newInstance(12,"李强",12);
        System.out.println("学生信息获取到:"+stu.getName());

  5.通过反射执行成员方法

        Constructor a = clazz1.getDeclaredConstructor(int.class,String.class,double.class);
        a.setAccessible(true);
        Student stu = (Student)a.newInstance(12,"李强",12);
        System.out.println("学生信息获取到:"+stu.getName());
        
        Method method = clazz1.getMethod("setName", String.class);
        method.invoke(stu, "芳芳");
        System.out.println("学生信息获取到:"+stu.getName());

 

分类:

技术点:

相关文章: