1. Java 反射的概念
Java 的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。反射被视为动态语言的关键。
2. 反射 API
(1) 反射中常用的类
java.lang.Class
:Java 中类也可以看作一种特殊的对象,Class 便表示的是类的对象。
java.lang.reflect.Constructor
:类的构造器。
java.lang.reflect.Field
:类的成员变量。
java.lang.reflect.Method
:类的成员方法。
(2) 获取类对象的三种方法
现在我在 top.mygld.demo.pojo
包下创建 User
类如下,方便接下来的演示:
package top.mygld.demo.pojo;
public class User {
private String name;
private int age;
}
然后接下来在主类中演示类对象的获取。
i)直接通过 类名.class
获取,例如:
Class c = User.class; //假设User是我们封装好的类
ii)通过 Class.forName("类全名")
获取,例如:
Class c = Class.forName("top.mygld.demo.pojo.User");
iii)通过 具体对象.getClass()
获取,例如:
User user = new User("小明",30);
Class c = user.getClass();
通过上述三种方法获得的对象 c
便是 User
类所对应的类对象,接下来我们可以通过调用 c
中的一些方法,来获取 User
类中的内部信息,进而对内部信息进行操控,这便是 Java
的反射。
(3) 类对象的基本信息
在第(2)步中,我们获取到了类对象 c
,那么如果我们直接打印输出 c
会得到什么?我们尝试一下。
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
public class ReflectionDemo {
public static void main(String[] args){
Class c = User.class;
System.out.println(c);
}
}
如上图,是输出了 class + 类全名
。这说明 Class
类重写了 toString
方法。
此外,Class
类还提供了两个主要的成员方法:getName
和 getSimpleName
,分别用来获得类对象的类全名和类简名效果如下。
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
public class ReflectionDemo {
public static void main(String[] args){
Class c = User.class;
System.out.println(c.getName());
System.out.println(c.getSimpleName());
}
}
(4) 通过反射获得目标类的构造器信息
通过反射机制,我们可以获得目标类的构造器(Constuctor)信息,如构造器的个数、构造器的参数情况(无参构造器、有参构造器)、有参构造器的参数个数、参数类型、访问修饰符等等。
为了便于测试,我接下来对 User
类进行改写,如下:
package top.mygld.demo.pojo;
public class User {
private String name;
private int age;
public User(){}
private User(String name){
this.name = name;
}
protected User(int age){
this.age = age;
}
public User(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
使用 Class
中的 getDeclaredConstructors()
方法可以获得目标类中的所有构造器的构造器对象数组,使用 Constructor
中的 getParameterCount()
可以获得对应构造器中的参数个数,具体如下:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;
public class ReflectionDemo {
public static void main(String[] args){
Class c = User.class;
Constructor[] cons = c.getDeclaredConstructors();
System.out.println("共有 " + cons.length + " 个构造器") ;
for(Constructor con : cons){
System.out.println(con + " 参数个数:" + con.getParameterCount());
}
}
}
当然如果只想获得 public
修饰的构造器,使用 Class
中的 getConstructors()
方法即可,这里将不再演示。
那么有一个问题,如果我想获得单个指定的构造器对象,该如何获取呢?其实只需要调用 getDeclaredConstructor
的重载方法 getDeclaredConstructor(Class<?>... parameterTypes)
即可,其中 parameterTypes
只需要传入 类名.class
或 基本数据类型.class
即可,具体如下:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
public class ReflectionDemo {
public static void main(String[] args) throws NoSuchMethodException {
Class c = User.class;
System.out.println(c.getDeclaredConstructor(int.class));
System.out.println(c.getDeclaredConstructor(String.class, int.class));
}
}
当然,如果想获得某个 public
修饰的构造器,也可以使用 Class
中的 getConstructor(Class<?>... parameterTypes)
来获得,其中它的参数可以为空,表示获得 public
修饰的无参构造。
- 注意:
getDeclaredConstructors
和getDeclaredConstructor
不受访问修饰符限制,getConstructors
和getConstructor
只能获取public
修饰的构造器。
(5) 通过反射创建目标类实例对象
已经获得到了目标类的构造器的对象,此时有人会有疑问了,用这个构造器对象我们能做什么?能做的其实非常多。
首先,通过调用 Constructor
中的 newInstance(Object... initargs)
方法,我们可以实例化一个目标类的对象,例如:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;
public class ReflectionDemo {
public static void main(String[] args) throws Exception{
Class c = User.class;
Constructor con = c.getDeclaredConstructor(String.class,int.class);
User kun = (User) con.newInstance("坤坤", 18);
System.out.println(kun);
}
}
那有人要问了,这不脱裤子放屁多此一举吗?我直接 new User("坤坤",18)
不就行了?还需要这么麻烦吗?
那接下来我要说点有意思的,我们观察到,在 User
类中,有一个 private
修饰的的构造方法,我们正常去执行如下代码一定会报错:
User user = new User("坤坤");
这是因为 private User(String name)
是私有的,不可以被外部直接访问,但是,通过反射我们可以临时绕过访问权限,使得能够调用这个构造器。调用 Constructor
中的 setAccessible(boolean)
即可,具体如下:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;
public class ReflectionDemo {
public static void main(String[] args) throws Exception{
Class c = User.class;
Constructor con = c.getDeclaredConstructor(String.class);
con.setAccessible(true);
System.out.println(con.newInstance("坤坤"));
}
}
- 注意,使用
con.setAccessible(true);
仅仅是临时修改了访问权限,并不会对目标类造成影响,该构造器仍然是private
的。
(6) 通过反射获取目标类成员变量
既然通过反射能获得目标类的构造方法信息,那么肯定也能获取目标类的成员变量(Field)信息。
其实,类比于构造器的获取,成员变量的获取基本类似:
通过调用 Field
中的 getDeclaredFields()
方法可以获得目标类下所有的成员变量信息;调用 Field
中的 getDeclaredField(String name)
可以获得成员变量中变量名为 name
的指定变量;通过调用 Field
中的 getFields()
方法可以获得目标类下所有的 public
修饰的成员变量信息;调用 Field
中的 getField(String name)
可以获得 public
修饰的成员变量中变量名为 name
的指定变量,具体如下:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ReflectionDemo {
public static void main(String[] args) throws Exception{
Class c = User.class;
System.out.println(c.getConstructor());
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
Field name = c.getDeclaredField("name");
System.out.println(name);
System.out.println(c.getFields().length);
//因为User中未设置public修饰的成员变量,这里不再演示getField()方法,大家可以自行尝试
}
}
- 注意:
getDeclaredField
和getField
方法参数列表均不能为空。
(7) 通过反射修改和获取目标类对象的成员变量的值
在第(6)步中,我们获得了目标类成员变量信息,那么我们能否可以根据这个成员变量对象,去修改或获取目标类对象的成员变量的值?答案是可以的,我们只需要使用 Field
中的 set(Object obj, Object value)
和 get(Object obj)
方法即可,具体如下(由于 User
中成员变量都是 private
的,不能直接修改,需要先通过调用 Field
中的 setAccessible(true)
来临时绕过访问权限):
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Field;
public class ReflectionDemo {
public static void main(String[] args) throws Exception{
Class c = User.class;
Field name = c.getDeclaredField("name");
name.setAccessible(true);
User user = new User("坤坤",18);
System.out.println(user);
name.set(user,"鸽鸽");
System.out.println(user);
System.out.println(name.get(user));
}
}
(9) 通过反射获取目标类成员方法
如果你详细看了上述 8 个部分内容,你应该能够猜到,获取成员方法(Method)信息也主要有四个方法:
分别是 Method
中的 getDeclaredMethods
、getDeclaredMethod
、getMethods
和 getMethod
,他们的功能与注意事项应该不言而喻了吧,为了方便演示,我先在 User
中添加若干方法,如下:
package top.mygld.demo.pojo;
public class User {
private String name;
private int age;
public User(){}
private User(String name){
this.name = name;
}
protected User(int age){
this.age = age;
}
public User(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void hello(){
System.out.println("你好,User");
}
}
然后我再在主类中测试:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionDemo {
public static void main(String[] args) throws Exception{
Class c = User.class;
Method[] declaredMethods = c.getDeclaredMethods();
for(Method m : declaredMethods){
System.out.println(m);
}
System.out.println(c.getMethod("setName", String.class));
Method[] methods = c.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println(c.getDeclaredMethod("hello"));
}
}
- 注意:
getDeclaredMethod
和getMethod
中,第一个参数是成员方法的名字,后面从第二个参数开始,表示这个成员方法的的参数列表的参数类型,如果没有参数列表,则上述方法只写第一个参数即可。
(10) 通过反射调用目标类对象的成员方法
通过反射,我们也可以调用 Method
中的 invoke(目标类对象,参数列表)
方法来调用目标类对象中的成员方法,具体如下:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Method;
public class ReflectionDemo {
public static void main(String[] args) throws Exception{
Class c = User.class;
Method setName = c.getMethod("setName", String.class);
User user = (User) c.newInstance();
System.out.println(user);
setName.invoke(user, "坤坤");
System.out.println(user);
}
}
3. 反射的作用
i) 基本作用:可以得到一个类的全部成分然后操作。(在上述步骤中已经体现)
ii) 可以破坏封装性。(因为可以修改类内部的成员的访问权限,因此会破坏类的封装性)
iii) 可以绕过泛型的约束,例如下述代码的执行结果:
package top.mygld.demo.test;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
ArrayList<String> a = new ArrayList<>();
a.add("hello");
Class c = a.getClass();
Method m = c.getDeclaredMethod("add", Object.class);
m.invoke(a, 123);
System.out.println(a);
}
}
4. 总结
综上,Java 反射的核心知识点主要包括四个方面:类对象(获取 Class
对象及其信息)、构造器(获取和操作类的构造方法)、成员变量(获取和操作类的字段)、以及成员方法(获取和调用类的方法)。
很多框架都是通过反射的方法实现的,例如 Spring Boot
。
通过反射 ,我们还可以自主设计一些框架,假如我要获取任意类的成员变量信息,我可以写一个比较简单的框架函数,如下:
package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
User user = new User("坤坤",18);
getInformation(user);
}
public static void getInformation(Object object) throws IllegalAccessException {
Class c = object.getClass();
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.getName() + ":" + field.get(object));
}
}
}
当然,上述只是举了个例子,这个函数代码可能对某些其他的类不适用。
”反射“为什么叫”反射“,我认为,正常调用某个对象的成员属性或成员方法,都是 对象.成员
的格式,而在反射中的格式基本都是 成员.方法(对象)
的形式,和正常的形式是反着的,所以叫 反射
。当然这只是我的一个个人理解,置于为啥真的叫”反射“,感兴趣的可以去查一下资料了~