1、反射的定义及作用
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取类的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。在Java运行时环境中,对于任意一个类的对象,可以通过反射获取这个类的信息。
反射的作用:Java反射机制允许程序在运行时透过Reflection APIs取得任意一个已知名称的class的内部信息,包括modifiers(如public、static等)、superclass(如Object)、实现的interfaces(如Serializable)、fields(属性)和methods(方法)(但不包括methods定义),可于运行时改变fields的内容,也可调用methods。
2、Class类的API简介
由类的加载可知,类加载的最终产品是位于方法区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序提供了访问方法区内的数据结构的接口。.class文件在硬盘中时是一个文件,当载入到内存中,可以认为是一个对象,是java.lang.Class类的对象。(在java中一切皆对象,类也是对象)
想要使用反射机制,就必须要先获取到该类的字节码文件对象,通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件,也就对应着一个Class类型的对象,也就是字节码文件对象。
获取字节码文件对象的三种方式。
1、Class clazz1 = Class.forName("全限定类名");
通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。
2、Class clazz2 = 类名.class;
当类被加载成.class文件时,再获取该字节码文件对象,该类处于字节码阶段。
3、Class clazz3 = 对象.getClass();
通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段。(java.lang.Object类(所有类的超类)定义了getClass()方法,任意一个Java对象都可以通过此方法获得它的Class对象。)
有了字节码文件对象才能获得类中所有的信息,我们在使用反射获取信息时,也要考虑使用上面哪种方式获取字节码对象合理,视不同情况而定。
Java Reflection API简介:
在JDK中,主要由以下类实现Java反射机制:
①Class类:代表一个类;
②Field类:代表类的成员变量(属性);
③Method类:代表类的方法;
④Constructor类:代表类的构造方法;
⑤Array类:提供了动态创建数组,以及访问数组元素的静态方法;
以上类中,Class类在java.lang包,其余位于java.lang.reflect包。
Class类是Reflection API中的核心类,主要有以下方法:
①getName():获取类的名字;
②getFields():获取类中public类型的属性;
③getDeclaredFields():获取类的所有属性(包括public、protected、default、private);
④getMethods():获取类中public类型的方法;
⑤getDeclaredMethods():获取类的所有方法;
⑥getMethod(String name,Class[] parameterTypes):获取类的指定方法,name:指定方法的名字,parameterType:指定方法的参数类型;
⑦getConstrutors():获取类中所有构造方法(包括private修饰的);
⑧getConstrutor(Class[] parameterTypes):获取类的指定构造方法,parameterTypes:指定构造方法的参数类型;
⑨newInstance():通过类默认的不带参数的构造方法创建该类的一个对象;
注:在访问私有属性和私有方法时,需要对访问的私有属性或方法设置setAccessible(true)使被反射的类抑制java的访问检查机制。否则会报IllegalAccessException异常。
3、反射的使用
创建一个java bean(如User类)和一个测试类(如ReflectTest类),如下所示(下面所有测试方法都是在ReflectTest类中定义的)。
User类:
package com.test; public class User { private String name = "init"; private int age; public User() {} public User(String name, int age) { super(); this.name = name; this.age = age; }private String getName() { return name; }private void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }@Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; }
}
测试类:
public class ReflectTest { private static Logger logger = Logger.getLogger(ReflectTest.class); //获取Class对象,在下面获取类名、构造函数、属性、方法等都需要该对象private static Class<User> userClass = User.class; //ReflectTest测试类中定义的测试方法在下面分别进行讲解,此处省略不写了
}
3.1、获取Class对象
在使用反射之前,必须获取类的字节码文件对象(此处是User.class文件对应的字节码文件对象),即Class类的对象(字节码文件对象都是Class类型的对象)。
由上面可知,获取class对象的方式主要有三种:
根据类名获取:类名.class
根据对象获取:对象.getClass()
根据全限定类名获取:Class.forName(“全限定类名”)
@Test
public void getClassTest() throws Exception { // 获取Class对象的三种方式,在ReflectTest类中就是使用第一种方式获取的logger.info("根据类名: \t" + User.class); logger.info("根据对象: \t" + new User().getClass()); logger.info("根据全限定类名:\t" + Class.forName("com.test.User"));
}
输出结果:
根据类名: class com.test.User
根据对象: class com.test.User
根据全限定类名: class com.test.User
3.2、获取类名
@Test
public void classTest() throws Exception { // 常用的方法 logger.info("获取全限定类名:\t" + userClass.getName()); logger.info("获取类名:\t" + userClass.getSimpleName()); logger.info("实例化:\t" + userClass.newInstance());
}
输出结果:
获取全限定类名: com.test.User
获取类名: com.test.User
实例化: User [name=init, age=0]
Class类的newInstance()方法是使用该类无参的构造函数创建对象,如果一个类没有无参的构造函数,就不能这样创建,可以调用Class类的getConstructor()方法获取一个指定的构造函数,然后再调用Constructor类的newInstance()方法创建对象,如下所示。
3.3、获取构造函数
@Test
public void constructorTest() throws Exception { // 获取全部的构造函数 Constructor<?>[] constructors = userClass.getConstructors(); //取消安全性检查,设置后才可以使用private修饰的构造函数,也可以单独对某个构造函数进行设置 Constructor.setAccessible(constructors, true); for (int i = 0; i < constructors.length; i++) { //获取构造函数中参数类型的字节码文件对象数组Class<?> parameterTypesClass[] = constructors[i].getParameterTypes(); System.out.print("第" + i + "个构造函数:\t ("); for (int j = 0; j < parameterTypesClass.length; j++) { System.out.print(parameterTypesClass[j].getName() + (j == parameterTypesClass.length - 1 ? "" : "\t")); } logger.info(")"); } // 调用构造函数,实例化对象 logger.info("实例化,调用无参构造:\t" + constructors[0].newInstance()); logger.info("实例化,调用有参构造:\t" + constructors[1].newInstance("韦德", 35));
}
输出结果:
第0个构造函数: ()
第1个构造函数: (java.lang.String int)
实例化,调用无参构造: User [name=init, age=0]
实例化,调用有参构造: User [name=韦德, age=35]
3.4、获取属性
@Test
public void fieldTest() throws Exception { //使用User类的无参构造函数创建User对象User user = userClass.newInstance(); // 获取当前类所有属性 Field fields[] = userClass.getDeclaredFields(); // 获取公有属性(包括父类) // Field fields[] = userClass.getFields(); // 取消安全性检查,设置后才可以获取或者修改private修饰的属性,也可以单独对某个属性进行设置 Field.setAccessible(fields, true); for (Field field : fields) { // 获取属性名、属性值、属性类型 logger.info("属性名:" + field.getName() + "\t属性值:" + field.get(user) + " \t属性类型:" + field.getType()); } // 获取具体某个属性名,此处获取"name"属性Field fieldUserName = userClass.getDeclaredField("name"); // 取消安全性检查,设置后才可以获取或者修改private修饰的属性,也可以批量对所有属性进行设置 fieldUserName.setAccessible(true); fieldUserName.set(user, "韦德"); logger.info("name属性的值:" + fieldUserName.get(user));// 可以直接对 private 的属性赋值 logger.info("修改属性后对象:\t" + user);
}
输出结果:
属性名:name 属性值:init 属性类型:class java.lang.String
属性名:age 属性值:0 属性类型:int
name属性的值:韦德
修改属性后对象: User [name=韦德, age=0]
Class.getField("属性名称")方法可以获取类中的指定属性(public修饰的),如果获取私有属性,可以用getDeclaedField("属性名称")方法获取。通过获取的属性的set(对象, "属性值")方法可以设置指定对象上该属性的值,如果是私有的,需要先调用setAccessible(true)设置访问权限,用获取的属性调用get(对象)可以获取指定对象中该属性的值。
3.5、获取方法
@Test
public void methodTest() throws Exception { User user = userClass.newInstance(); // 获取当前类的所有方法 Method[] methods = userClass.getDeclaredMethods(); // 获取公有方法(包括父类) // Method[] methods = userClass.getMethods(); // 取消安全性检查,设置后才可以调用private修饰的方法,也可以单独对某个方法进行设置 Method.setAccessible(methods, true); for (Method method : methods) { // 获取方法名和返回类型,获取参数类型:getParameterTypes logger.info("方法名:" + method.getName() + " \t返回类型:" + method.getReturnType().getName()); } // 获取无参方法 Method getMethod = userClass.getDeclaredMethod("getName"); // 取消安全性检查,设置后才可以调用private修饰的方法,也可以批量对所有方法进行设置 getMethod.setAccessible(true); // 调用无参方法 logger.info("调用getName方法:" + getMethod.invoke(user)); // 获取有参方法 Method setMethod = userClass.getDeclaredMethod("setName", String.class); // 取消安全性检查,设置后才可以调用private修饰的方法,也可以批量对所有方法进行设置 setMethod.setAccessible(true); // 调用有参方法 logger.info("调用setName方法:" + setMethod.invoke(user, "韦德")); logger.info("通过set方法修改属性后对象:\t" + user);
}
输出结果:
方法名:toString 返回类型:java.lang.String
方法名:setAge 返回类型:void
方法名:getAge 返回类型:int
方法名:getName 返回类型:java.lang.String
方法名:setName 返回类型:void
调用getName方法:init
调用setName方法:null
通过set方法修改属性后对象: User [name=韦德, age=0]
Class.getMethod("方法名",方法参数类型的Class类型)和Class.getDeclaredMethod("方法名",方法参数类型的Class类型)方法可以获取类中的指定方法,如果为私有方法,则需要使用setAccessible(true)打开一个权限。例如,User类中的private void setName(String name)方法,方法名是setName,方法参数类型是String类型,参数类型的Class类型就是String.class,而且是private私有方法,所以使用反射获取该方法的代码如下所示:Method getMethod = userClass.getDeclaredMethod("setName", String.class)。
用获取的方法名调用invoke(方法所属的对象, 参数值)方法可以对该方法操作,如果方法没有参数值,可以不写参数值。