反射
反射允许对成员变量,成员方法和构造方法的信息进行编程访问
获取class对象
反射是通过class字节码来获取哪些信息的
类加载各阶段完成的功能
加载阶段:将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,此过程由类加载器完成。
连接阶段:又分为验证、准备、解析三个小阶段,此阶段会将类的二进制数据合并到 JRE 中。
初始化阶段:JVM 负责对类的静态成员进行初始化。
如下图所示:
加载阶段
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、jar 包、甚至网络文件)转换为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。
5 连接阶段——验证
5.3 连接阶段——准备
JVM 会在该阶段对静态变量分配内存并进行默认初始化(不同数据类型会有其默认初始值,如:int ---- 0,boolean ---- false 等)。这些变量的内存空间会在方法区中分配。
举例如下:
public class ClassLoad { public static void main(String[] args) { // 属性=成员变量=字段 // 类加载的连接阶段-准备,属性是如何加载的 public int n1 = 10; public static int n2 = 20; public static final int n3 = 30; } }
代码说明:
n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
n2 是静态变量,在该阶段 JVM 会为其分配内存,n2 默认初始化的值为 0 ,而不是 20
n3 被 static final 修饰,是常量, 它和静态变量不一样, 其一旦赋值后值就不变,因此其默认初始化 n3 = 30
连接阶段——解析
JVM 将常量池内的符号引用替换为直接引用的过程。
初始化阶段
在初始化阶段,JVM 才会真正执行类中定义的 Java程序代码,此阶段是执行()
方法的过程。
()
方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并进行合并的过程。
JVM 会保证一个类的 ()
方法 在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 ()
方法,其他线程都要阻塞等待,直到活动线程执行 ()
方法完毕。
举例如下:
public class ClassLoad { public static void main(String[] args) throws ClassNotFoundException { System.out.println(B.num);// 直接使用类的静态属性,也会导致类的加载 } } class B { static { // 静态代码块 System.out.println("B 静态代码块被执行"); num = 300; } static int num = 100;// 静态变量 public B() {// 构造器 System.out.println("B() 构造器被执行"); }
输出如下:
B 静态代码块被执行 100
注意没有使用到类,类不会被加载的
代码说明:
- 加载阶段:加载 B类,并生成 B的 class对象
- 连接阶段:进行默认初始化 num = 0
- 初始化阶段:执行
() 方法
,该方法会依次自动收集类中的所有静态变量
的赋值操作和静态代码块
中的语句,并合并。如下:
clinit() { System.out.println("B 静态代码块被执行"); num = 300; num = 100; }
- 合并后: num = 100
注意:加载类的时候,具有同步机制控制。如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象 synchronized (getClassLoadingLock(name)) { //.... } }
访问字段
对任意的一个Object
实例,只要我们获取了它对应的Class
类对象,就可以获取它的一切信息。
我们先看看如何通过Class类对象获取其对应的类定义的字段信息。Class类提供了以下几个方法来获取字段:
Field getField(name):根据字段名获取某个 public 的 field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类)
Field[] getFields():获取所有 public 的 field(包括父类)
Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)
示例:
public class Main { public static void main(String[] args) throws Exception { Class stdClass = Student.class; // 获取public字段"score": System.out.println(stdClass.getField("score")); // 获取继承的public字段"name": System.out.println(stdClass.getField("name")); // 获取private字段"grade": System.out.println(stdClass.getDeclaredField("grade")); } } class Student extends Person { public int score; private int grade; } class Person { public String name; }
- 上述代码首先获取
Student
的Class
实例,然后,分别获取public
字段、继承的public
字段以及private
字段,打印出的Field
类似下面:
public int Student.score public java.lang.String Person.name private int Student.grade
一个Field对象包含了一个字段的所有信息:
- getName():返回字段名称,例如,“name”;
- getType():返回字段类型,也是一个Class类对象,例如,String.class;
- getModifiers():返回字段的修饰符,它是一个int,不同的 bit 表示不同的含义。
以
String
类的value
字段为例,它的定义是:
public final class String { private final byte[] value; }
我们用反射获取该字段的信息,代码如下:
Field f = String.class.getDeclaredField("value"); f.getName(); // "value" f.getType(); // class [B 表示byte[]类型 int m = f.getModifiers(); Modifier.isFinal(m); // true Modifier.isPublic(m); // false Modifier.isProtected(m); // false Modifier.isPrivate(m); // true Modifier.isStatic(m); // false
1. 获取字段值
利用反射拿到字段的一个Field
类对象只是第一步,我们还可以拿到一个实例对象对应的该字段的值。
例如,对于一个
Person
类对象,我们可以先拿到其name
字段对应的Field
,再获取这个Person
类对象的name
字段的 值:
import java.lang.reflect.Field; public class Main { public static void main(String[] args) throws Exception { Person p = new Person("Xiao Ming"); Class c = p.getClass(); Field f = c.getDeclaredField("name");// 获取 private String name; Object value = f.get(p); System.out.println(value); // "Xiao Ming" } } class Person { private String name; public Person(String name) { this.name = name; } }
上述代码先获取Person类对应的Class类对象,再通过该Class类对象获取Field类对象,然后,用Field.get(Object)获取指定Person类对象的指定字段的值。
运行代码,如果不出意外,会得到一个IllegalAccessException异常,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public,或者,在调用Object value = f.get§;前,先写一句:
f.setAccessible(true);
调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。
可以试着加上上述语句,再运行代码,就可以打印出private字段的值。
有童鞋会问:如果使用反射可以获取private字段的值,那么类的封装还有什么意义?
- 答案是一般情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private这些访问权限修饰符决定是否允许访问字段,这样就达到了数据封装的目的。
- 而反射是一种非常规的用法,使用反射,首先代码非常繁琐;其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标对象任何信息的情况下,获取特定字段的值。
此外,setAccessible(true)可能会失败。 如果 JVM 运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证 JVM 核心库的安全。
2.设置字段值
通过 Field 类对象既然可以获取到指定对象的字段值,自然也可以设置字段的值。
设置字段值是通过
Field.set(Object, Object)
实现的,其中第一个Object
参数是指定的对象,第二个Object
参数是待修改的值。示例代码如下:
import java.lang.reflect.Field; public class Main { public static void main(String[] args) throws Exception { Person p = new Person("Xiao Ming"); Class c = p.getClass(); Field f = c.getDeclaredField("people"); f.setAccessible(true);// 获取 private String name; // Object value = f.get(p); f.set(p,new People()); System.out.println(f.get(p)); // "Xiao Ming" } } class Person { protected String name; private People people; public Person(String name) { this.name = name; } } class People{ private String name; public People(){ this.name="qq";} @Override public String toString() { return name; } }
可以通过反射给Person的字段赋值
但如果赋值的类型不是Person字段的类型的呢
这里我给People people 赋值的是字符串而不是People类型
public static void main(String[] args) throws Exception { Person p = new Person("Xiao Ming"); Class c = p.getClass(); Field f = c.getDeclaredField("people"); f.setAccessible(true);// 获取 private String name; // Object value = f.get(p); f.set(p,"new People()"); System.out.println(f.get(p)); // "Xiao Ming" }
3.小结
Java 的反射 API 提供的Field类封装了对应的类定义的全部字段的所有信息:
通过Class类对象的方法可以获取Field类对象:getField(),getFields(),getDeclaredField(),getDeclaredFields();
通过Field类对象可以获取类定义字段信息:getName(),getType(),getModifiers();
通过Field类对象可以读取或设置某个对象的字段的值,如果存在访问限制,则需要调用setAccessible(true)来访问非public字段。
通过反射读写字段是一种非常规的方法,它会破坏对象的封装。
调用方法
我们已经能通过Class类的Field类对象获取其对应的类class中定义的所有字段信息,同样的,可以通过Class类获取所有Method信息。Class类提供了以下几个方法来获取类class中定义的Method:
Method getMethod(name, Class…):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
public class Main { public static void main(String[] args) throws Exception { Class stdClass = Student.class; // 获取 public方法 getScore,形参类型为 String: System.out.println(stdClass.getMethod("getScore", String.class)); // 获取继承的 public方法 getName,无参数: System.out.println(stdClass.getMethod("getName")); // 获取 private方法 getGrade,形参类型为 int: System.out.println(stdClass.getDeclaredMethod("getGrade", int.class)); } } class Student extends Person { public int getScore(String type) { return 99; } private int getGrade(int year) { return 1; } } class Person { public String getName() { return "Person"; } }
一个Method类对象包含一个方法的所有信息:
- getName():返回方法名称,例如:“getScore”;
- getReturnType():返回方法的返回值类型,也是一个Class实例,例如:String.class;
- getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
- getModifiers():返回方法的修饰符,它是一个int,不同的 bit 表示不同的含义。
Java反射机制(2)https://developer.aliyun.com/article/1530900