前言:
在学习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 enumis a kind of* class and an annotation is a kind of interface. Every array also* belongs to a classthat 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.*
*
{@code Class} has no publicconstructor. 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创建。
反射作用
可以通过反射得到类成员变量,成员方法以及构造方法并可以操作他们,即使是私有的也可以访问并操作。
提高代码灵活性
反射示例
首先创建一个测试Student类:
public classStudent {private intage;privateString name;private double score; //三个私有属性
public intgetAge() {returnage;
}public void setAge(intage) {this.age =age;
}publicString getName() {returnname;
}public voidsetName(String name) {this.name =name;
}public doublegetScore() {returnscore;
}public void setScore(doublescore) {this.score =score;
}public Student(){//公共不带参构造
}private Student(int age,String name,double score){//私有带参构造
this.age =age;this.name =name;this.score =score;
}
}
三种方式获取Class对象:
Class clazz1 = Class.forName("reflection.Student");
Class clazz2= Student.class;
Student student= newStudent();
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());