Java 反射机制
Posted Lucas小毛驴博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 反射机制相关的知识,希望对你有一定的参考价值。
一.Java反射机制介绍
? 反射机制主要在于这个反字.
? 一般情况下,我们使用类的时候都是知道它是个什么类,比如定义了一个Person类,在使用的时候我们已经知道这个Person类是用来做什么的,于是我们就可以直接new一个Person类的实例对象,然后对这个实例对象进行操作,这是常规的操作。
public class Person {
public void say() {
System.out.println("hello java");
}
public static void main(String[] args) {
Person p = new Person();
p.say();
}
}
? 而反射,就是与常规情况不同,在一开始我们并不知道要初始化的类对象是什么,导致我们没法使用new去实例化,但可以使用JDK提供的反射API进行调用,比如如下代码:(相关API下面会提及)
public class Person {
public void say() {
System.out.println("hello java");
}
}
public class ReflectTest {
public static void main(String[] args) throws Exception{
// 获取class类对象
Class<?> cls = Class.forName("Person");
// 获取方法
Method method = cls.getMethod("say");
// 获取构造器
Constructor<?> constructor = cls.getConstructor();
// 创建对象
Object obj = constructor.newInstance();
// 执行对象的方法
method.invoke(obj); // 输出 hello java
}
}
? 以上两种方式效果都是一样的,区别就在第一段代码在还没运行时候就已经确定了要运行的类是Person,而第二段代码是在运行时候,由字符串传递了类名,才知道要执行的类是哪个。
? 所以,Java的反射机制就是指在运行中,才会知道要运行的类是哪一个,而且可以在运行时候获取这个类的所有属性和方法。
二.Java反射机制相关知识
理解Class类
首先得清楚,在java中,除了
int
等基础类型外,其他类型全都是class
.class是由JVM在执行过程中动态加载的,每加载一种class,JVM就会为其创建一个Class类型的实例,并关联起来,这里的Class类型,也是class的一种,只是名字和class相似(Java是大小写敏感的)。
第二,Class类的实例内容就是创建的类的类型信息,包括类名、包名、父类、实现的接口、所有方法、所有成员变量等。
比如我们创建了一个Person类,那么java就会生成一个内容是Person的Class类实例,这也意味着如果我们获取了某个类对象的Class实例,我们就可以知道这个
Class
实例所对应的class
的所有信息(这就是反射)。第三,Class类的对象不像普通类一样,是不可以使用new来创建的,它只能由JVM创建,因为这个类并没有public的构造方法。
如何获取Class类对象
有三种方式可以获取Class类对象:
类名.class:通过类名的属性class获取
比如:
Class cls = String.class;
Class.forName():通过Class类的静态方法forName获取
用于已知一个类的完整类名(包括包名)
Class cls = Class.forName("java.lang.String");
对象.getClass():通过实例对象的getclass方法获取
用于已知实例对象的情况。
String str = "hello"; Class cls = str.getClass();
注意:因为Class类实例在JVM中是唯一的,所以上面三种方法获取的Class实例都是同一个的。
JVM动态加载
? JVM在执行java程序时候,并不是一次性把所有的class全部加载到内存的,而是在第一次需要用到class时候才会加载,比如下面的例子,执行ReflectTest.java时,用到了main,所以JVM会先把
ReflectTest.class
加载到内存,但不会加载Person.class
,当执行到了new Person()
时候,发现需要用到Person类时候,才会去加载Person.class,这就是JVM动态加载的特性。public class Person { public void say() { System.out.println("hello java"); } } public class ReflectTest { public static void main(String[] args) { Person p = new Person(); p.say(); } }
? 正是有JVM动态加载的特性,我们才可以在运行时根据条件来加载不同的类
三.Java反射机制的使用
? 现在我们已经知道了怎么获取Class实例了,也知道了我们可以获取class的所有信息,但我们一般是通过这个Class实例来获取成员变量Field、构造器Constructor以及成员方法Method。
? 在Class类实例中,是将类的各个组成部分(成员变量,成员方法,构造器等)封装为其他对象的,所以我们通过Class实例获取到也是一个个对象,每个对象内有各自的方法。
? 下面就看看如何获取这些信息:
获取成员变量Field
Field[] getFields()
:获取所有public修饰的成员变量Field getField(String name)
:获取指定名称的public修饰的成员变量Field getDeclaredFields()
:获取所有的成员变量,不考虑修饰符Field getDeclaredField(String name)
:获取指定名称的成员变量,不考虑修饰符
上面说过,Class类实例中式将类的每一部分都封装为对象,那么每一个成员变量,从Class类实例中获取到的也是一个对象Field,要操作成员变量,就是操作这个Field对象的成员,其主要方法有以下几个:
void set(Object obj, Object value)
:设置值Object get(Object obj)
:获取值void setAccessible(boolean flag)
:设置允许访问
public class Person { public int age; private String name; public void say() { System.out.println("hello java"); } }
import java.lang.reflect.*; public class ReflectTest { public static void main(String[] args) throws Exception{ Class<?> cls = Class.forName("Person"); Constructor<?> constructor = cls.getConstructor(); Object obj = constructor.newInstance(); // 获取所有public的成员变量 Field[] fields = cls.getFields(); for (Field f : fields) { System.out.println(f.getName()); } // 输出:age // 获取指定名字的public成员变量 Field field = cls.getField("age"); field.set(obj, 20); System.out.println(field.get(obj));// 输出:20 // 获取所有的成员变量,不考虑修饰符 Field[] fields1 = cls.getDeclaredFields(); for(Field f : fields1) { System.out.println(f.getName()); }// 输出: age name // 获取指定名字的成员变量,不考虑修饰符,这里操作私有变量name Field f = cls.getDeclaredField("name"); f.setAccessible(true); f.set(obj, "hello"); System.out.println(f.get(obj)); // 输出:hello } }
注意:
? 在获取private修饰的成员变量时,如果没有加上这一句f.setAccessible(true);是会报一个异常
java.lang.IllegalAccessException: Class ReflectTest can not access a member of class Person with modifiers "private"
,是因为正常情况下,是无法访问Person类的private字段的,而setAccessible(true)
的意思是不管字段是不是public,一律运行访问获取构造器Constructor
Constructor<?>[] getConstructors()
:获取所有public的构造器Constructor<T> getConstructor(Class<?>... parameterTypes)
:获取指定参数的public的构造器Constructor<?>[] getDeclaredConstructors()
:获取所有的构造器Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
:获取指定参数的构造器,不考虑修饰符
注意:这里的参数
Class<?>... parameterTypes
是指可变参数,可以有一个,也可以没有,类型是Class类型的,比如参数为int,则为int.class,就是上述说的三种获取Class类对象的方法。同样,获取构造器返回的是Constructor对象,也有几个常用操作的方法:
T newInstance(Object... initargs)
::创建一个实例void setAccessible(boolean flag)
:设置允许访问
public class Person { public int age; private String name; public Person() { System.out.println("public无参构造方法"); } public Person(int age,String name) { System.out.println("age = " + age + ",name = " + name); } }
import java.lang.reflect.*; public class ReflectTest { public static void main(String[] args) throws Exception{ Class<?> cls = Class.forName("Person"); // 获取所有public修饰的构造方法 Constructor<?>[] constructors = cls.getConstructors(); // 获取无参构造方法 // Constructor<?> c0 = cls.getConstructor(); // c0.newInstance(); cls.newInstance(); // 对于空参的构造方法生成对象的,可以直接通过Class对象的newInstance()生成 // 获取指定参数列表的构造方法 Constructor<?> c = cls.getConstructor(int.class, String.class); c.newInstance(10, "Apple"); } }
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法。
获取成员方法Method
Method[] getMethods()
:获取所有public的方法(包括父类)Method getMethod(String name, Class<?>... parameterTypes)
:获取public的方法(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有方法(不包括父类)Method getDeclaredMethod(String name, Class<?>... parameterTypes)
:获取当前类的某个方法(不包括父类)
获取方法相对应返回的对象是Method,主要有以下几个方法调用:
Object invoke(Object obj, Object... args)
:执行方法String getName()
:获取方法名void setAccessible(boolean flag)
:设置允许访问
父类Person:
public class Person { public int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } public String getName() { return this.name; } }
子类Student:
public class Student extends Person { private int score; public Student(int age, String name, int score) { super(age, name); this.score = score; } public static void staticMethod(){ System.out.println("static Method"); } public int getScore() { return score; } public void setScore(int score) { this.score = score; } private int getAge() { return this.age; } }
反射操作:
import java.lang.reflect.*; public class ReflectTest { public static void main(String[] args) throws Exception{ Class<?> cls = Student.class; // 获取构造器 Constructor<?> con = cls.getConstructor(int.class, String.class); // 生成实例 Object obj = con.newInstance(20, "zhangsan",90); // 获取类的所有public方法(包括父类方法) Method[] methods = cls.getMethods(); for (Method m : methods) { System.out.print(m.getName() + " "); } // 输出:getScore setScore staticMethod getName wait wait wait equals toString hashCode getClass notify notifyAll // 获取子类某个public方法 Method getScoreMethod = cls.getMethod("getScore"); System.out.println(getScoreMethod.invoke(obj)); // 输出: 90 // 获取父类某个public方法 Method getNameMethod = cls.getMethod("getName"); System.out.println(getNameMethod.invoke(obj)); // 输出:zhangsan // 获取当前类的所有方法 Method[] methods2 = cls.getDeclaredMethods(); for (Method m : methods2) { System.out.print(m.getName() + " "); } // 输出:getAge setScore staticMethod getScore // 获取当前类的指定的方法 私有方法--getAge Method getAgeMethod = cls.getDeclaredMethod("getAge"); getAgeMethod.setAccessible(true); System.out.println(getAgeMethod.invoke(obj)); // 输出: 20 // 获取静态方法 Method getStaticMethod = cls.getDeclaredMethod("staticMethod"); getStaticMethod.invoke(null); // 输出: static Method } }
注意:如果调用静态方法,是不需要指定实例对象的,所以invoke方法传入的第一个参数设置为null。
四.总结
? 从以上的几个例子可看出,一般我们使用反射获取一个对象的步骤有以下几步:
获取类的Class对象实例(有三种方式)
比如:
Class cls = Student.class
;使用Class对象实例获取构造方法对象
比如:
Constructor constructor = cls.getConstructor();
使用构造器方法对象的newInstance()方法创建对象
比如:
Object obj = constructor.newInstance();
进行相关操作,获取成员变量,获取方法,调用方法等。
以上是关于Java 反射机制的主要内容,如果未能解决你的问题,请参考以下文章