反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象, 都能够调用它的所有属性和方法,这种动态获取类信息和动态调用对象方法的机制我们称之为Java语言的反射机制。我们知道, Class类是反射得以实现的重要部分,而对应的class对象是在JVM在的类加载过程的加载阶段自动为我们生成的,Class类与java.lang.reflect反射包类库一起支持着反射的实现
在反射包中有几个重要的类结构,其中Constructor类表示的是class对象所表示类的构造方法,通过它可以在运行时动态创建对象;Field类表示的是class对象所表示类的成员变量,通过它可以在运行时动态修改成员变量的属性值;Method类表示的class对象所表示类的成员方法,通过它可以动态调用对象的方法。下面我们先来讲解一下Class类的相关内容
Class类简介
在java中,万物皆对象,从某种意义上来说,java有两种对象:实例对象和class对象。其中class对象代表每个类的类型信息,它包含与类有关的信息,其中实例对象也是通过class对象来创建的。class类没有公共构造方法,class对象是由jvm以及通过类加载器中的defineClass方法自动构建的,一般我们的类加载到内存要经历如下三个阶段:
1.加载:这个是由类加载器(ClassLoader)执行的,通过一个类的全限定名来找到此类字节码文件,并利用字节码文件在java堆中创建一个class对象
2.链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用
3.初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量
加载过程图示如下:
所有的class类都是在第一次使用的时候才会加载,当程序创建第一个对类的静态成员的引用时,当然new一个对象的时候也是对类的静态成员的引用,然后就会加载对应的类,首先检查此类是否已经被加载过,如果加载过了就直接使用,如果没有加载过才去加载(懒加载),并且所有通过相同类型的Class创建的对象都使用同一个class对象
如何获取class对象
获取class对象的方式有三种:
1.Class.forName("类的全限定名")
2.实例对象.getClass()
3.类名.class
上面三种获得class对象的方式,其中第三种方式不会不会触发类的初始化,第一和第二都会,下面我们用例子来验证一下:
public class ReflectDemo {
public static void test() throws Exception {
//内部类需要使用$符号连接
Class<?> clazz01 = Class.forName("com.androidstudydata.reflect.ReflectDemo$User01");
Class<?> clazz02 = new User02().getClass();
Class<?> clazz03 = User03.class;
}
static class User01 {
//初始化
static {
LogUtils.d("初始化User01类");
}
}
static class User02 {
//初始化
static {
LogUtils.d("初始化User02类");
}
}
static class User03 {
//初始化
static {
LogUtils.d("初始化User03类");
}
}
static class User {
//初始化
static {
LogUtils.d("初始化User类");
}
private int age;
private String name;
public User() {
super();
}
public User(String name) {
super();
this.name = name;
}
/**
* 私有构造
*
* @param age
* @param name
*/
private User(int age, String name) {
super();
this.age = age;
this.name = name;
}
//..........省略set 和 get方法
}
}
运行结果如下:
可以得知,用第三种方式确实不会初始化class类,应该来说这种方式效率更高。初始化是类加载的最后阶段了,也就是说完成这个阶段类就已经被加载到内存了(class对象在加载阶段已经被创建),此时才可以对类进行必要的各种操作(包括new对象,调用静态成员),这个阶段才是真正执行类中定义的java代码或者字节码
关于类的初始化,java虚拟机严格规定了有且只有5中情形必须对类进行初始化:
1.使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程
2.使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要
3.当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化
4.当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类
5.当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化
Java反射技术的应用
反射常见的应用场景是在框架中实现工厂模式和动态代理模式,一般都会用到反射包中的几个重要类:Constructor、Field和Method,下面来看看在反射中是如何使用这几个类结构的
1.Constructor类及其用法
Constructor类反映的是class对象所表示类的构造方法,获取Constructor对象用class对象的方法获得,class类和Constructor类的主要相关方法表示如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
static Class<?> | forName(String className) | 返回指定限定名的类或接口相关联的 Class 对象。 |
Constructor<T> | getConstructor(Class<?>... parameterTypes) | 返回指定参数类型、具有public访问权限的构造函数对象 |
Constructor<?>[] | getConstructors() | 返回所有具有public访问权限的构造函数的Constructor对象数组 |
Constructor<T> | getDeclaredConstructor(Class<?>... parameterTypes) | 返回指定参数类型、所有声明的(包括private)构造函数对象 |
Constructor<?>[] | getDeclaredConstructor() | 返回所有声明的(包括private)构造函数对象 |
下面通过简单的例子来展示Constructor的使用:
public class ReflectDemo {
public static void test() throws Exception {
Class<?> personClazz = Person.class;
LogUtils.d("返回指定参数类型、具有public访问权限的构造函数对象");
//返回指定参数类型、具有public访问权限的构造函数对象
Constructor cs01 = personClazz.getConstructor(String.class);
//创建对象
Person person01 = (Person) cs01.newInstance("锤子");
person01.setAge(100);
LogUtils.d("person01=" + person01.toString());
LogUtils.d("返回指定参数类型、所有声明的(包括private)构造函数对象");
//返回指定参数类型、所有声明的(包括private)构造函数对象
Constructor cs02 = personClazz.getDeclaredConstructor(int.class, String.class);
Person person02 = (Person) cs02.newInstance(25, "二狗");
LogUtils.d("perosn02=" + person02.toString());
LogUtils.d("返回所有声明的(包括private)构造函数对象");
Constructor[] constructors = personClazz.getDeclaredConstructors();
//查看每个构造方法信息
for (int i = 0; i < constructors.length; i++) {
//获取构造函数参数类型
Class[] classes = constructors[i].getParameterTypes();
LogUtils.d("构造方法" + i + "信息=" + constructors[i].toString());
LogUtils.d("参数类型:");
for (int j = 0; j < classes.length; j++) {
LogUtils.d(classes[j].getName());
}
}
}
static class Person {
//初始化
// static {
// LogUtils.d("初始化User类");
// }
private int age;
private String name;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
/**
* 私有构造
*
* @param age
* @param name
*/
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
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;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
}
运行结果:
2.Field类及其用法
field类用来获取一个类的成员变量信息,通过它可以动态地改变类的成员变量属性,Class类与Field类相关的方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Field | getDeclaredField(String name) | 获取指定名称的(包含private修饰的)字段,不包括继承的字段 |
Field[] | getDeclaredField() | 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 |
Field | getField(String name) | 获取指定name名称、具有public修饰的字段,包含继承字段 |
Field[] | getField() | 获取修饰符为public的字段,包含继承字段 |
代码示例如下:
public class ReflectDemo {
public static void test() throws Exception {
Class studenClazz = Student.class;
//获取指定名称的(包含private修饰的)字段,不包括继承的字段
Field field01 = studenClazz.getDeclaredField("score");
LogUtils.d("指定名称不包含继承包含private修饰的score字段=" + field01);
//获取指定name名称、具有public修饰的字段,包含继承字段
Field field02 = studenClazz.getField("name");
LogUtils.d("指定名称字段public修饰的name,包含继承的=" + field02);
//获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
Field[] fields = studenClazz.getDeclaredFields();
for (Field field : fields) {
LogUtils.d("不包括继承包含private的字段=" + field);
}
//获取修饰符为public的字段,包含继承字段
Field[] fields1 = studenClazz.getFields();
for (Field field : fields1) {
LogUtils.d("包含继承public修饰的字段=" + field);
}
}
//继承Person
static class Student extends Person {
public String grade;
private int score;
}
static class Person {
//初始化
// static {
// LogUtils.d("初始化User类");
// }
private int age;
public String name;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
/**
* 私有构造
*
* @param age
* @param name
*/
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
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;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
}
执行结果如下:
如果我们不期望获得父类的字段,则使用getDeclaredField(String name)和getDeclaredField()方法,如果希望获得包括父类的字段则用getField(String name)和getField()方法,但是也只能获得父类的public修饰的字段,不能获得私有字段
还有一些Field类比较常用的方法:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
void | set(Object obj, Object value) | 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 |
Object | get(Object obj) | 返回指定对象上此 Field 表示的字段的值 |
Class<?> | getType() | 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。 |
boolean | isEnumConstant() | 如果此字段表示枚举类型的元素则返回 true;否则返回 false |
String | toGenericString() | 返回一个描述此 Field(包括其一般类型)的字符串 |
String | getName() | 返回此 Field 对象表示的字段的名称 |
Class<?> | getDeclaringClass() | 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 |
void | setAccessible(boolean flag) | 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性 |
示例代码如下:
public class ReflectDemo {
public static void test() throws Exception {
Class studenClazz = Student.class;
Student student= (Student) studenClazz.newInstance();
student.setName("小明");
Field fieldName=studenClazz.getField("name");
LogUtils.d("name字段的值="+fieldName.get(student));
//重新设置name字段的值
fieldName.set(student,"锤子");
LogUtils.d("改变后的name字段的值="+student.getName());
LogUtils.d("name字段类型="+fieldName.getType());
LogUtils.d("name字段是否为枚举="+fieldName.isEnumConstant());
//设置访问性
fieldName.setAccessible(false);
LogUtils.d("改变访问属性的name字段="+fieldName.isAccessible());
}
//继承Person
static class Student extends Person {
public String grade;
private int score;
}
static class Person {
//初始化
// static {
// LogUtils.d("初始化User类");
// }
private int age;
public String name;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
/**
* 私有构造
*
* @param age
* @param name
*/
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
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;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
}
执行结果:
3.Method类及其使用方法
Method类提供了可以获取类或接口的方法的信息,通过它可以动态调用对象的方法。下面是class类和method类的相关方法:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Method | getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一个指定参数的Method对象,不包括继承父类的方法 。 |
Method[] | getDeclaredMethod() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
Method | getMethod(String name, Class<?>... parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定public方法。 |
Method[] | getMethods() | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的public方法。 |
代码示例如下:
public class ReflectDemo {
public static void test() throws Exception {
LogUtils.d("\n\n\n\n");
//Method相关测试
Class classStu = Student.class;
//返回一个指定参数的Method对象
Method method01 = classStu.getDeclaredMethod("setGrade", String.class);
LogUtils.d("指定string类型参数的setGrade方法=" + method01);
//返回所有的不包括继承的包括private成员方法
Method[] methods = classStu.getDeclaredMethods();
for (Method method:methods){
LogUtils.d("返回所有方法,不包括继承但包括private=" +method+"\n");
}
//返回包括继承的指定的public成员方法
Method method02 = classStu.getMethod("setAge", int.class);
LogUtils.d("返回指定的包括继承的public成员方法=" + method02);
//返回包括继承的所有public成员方法
Method[] methods1 = classStu.getMethods();
for (Method method : methods1) {
LogUtils.d("返回包括继承的所有public成员方法=" + method+"\n");
}
}
//继承Person
static class Student extends Person {
public String grade;
private int score;
private String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
static class Person {
//初始化
// static {
// LogUtils.d("初始化User类");
// }
private int age;
public String name;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
/**
* 私有构造
*
* @param age
* @param name
*/
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
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;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
}
执行结果如下:
从执行结果来看,一般带Declared字面的方法,代表不包括继承父类的信息,但包括private方法的信息,不带Declared字面的方法,代表包括从父类继承的信息,但限定为public修饰的信息
Method类相关常用方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Object | invoke(Object obj, Object... args) | 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 |
Class<?> | getReturnType() | 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型 |
Type | getGenericReturnType() | 返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。 |
Class<?>[] | getParameterTypes() | 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组 |
Type[] | getGenericParameterTypes() | 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型 |
String | getName() | 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称 |
boolean | isVarArgs() | 判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。 |
String | toGenericString() | 返回描述此 Method 的字符串,包括类型参数。 |
好了,关于反射的相关知识就到此结束吧!