Java反射机制
一、Java反射机制是什么?
Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,大家应该先了解编译期和运行期两个概念:
- 编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
- 运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中:
- 对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意方法和属性;
这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java 反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,在 ORM 中间件的实现中,运用 Java 反射机制可以读取任意一个 JavaBean 的所有属性,或者给这些属性赋值。

Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
如果要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。先要获取到每一个字节码文件(.class)对应的 Class 类型的对象.
Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,返回同一个类型为 Class 的对象。如下:
public class Test02 { private static Object User; public static void main(String[] args) throws NoSuchMethodException{ User user = new User("admin", "123312"); Class userClass = user.getClass();// user为 User 类的对象 System.out.println("内部类:获取所有权限为 public 的内部类: "+ Arrays.toString(userClass.getClasses())); System.out.println("内部类:获取所有内部类: "+ Arrays.toString(userClass.getDeclaredClasses())); System.out.println("内部类的声明类:如果该类为内部类,则返回它的成员类,否则返回 null"+userClass.getDeclaringClass()); System.out.println("类名称:"+userClass.getName()); System.out.println("继承类:"+userClass.getSuperclass()); System.out.println("实现接口:"+ Arrays.toString(userClass.getInterfaces())); System.out.println("构造方法:获取所有权限为 public 的方法:"+userClass.getMethod("getName")); System.out.println("构造方法:获取当前对象的所有方法:"+ Arrays.toString(userClass.getMethods())); System.out.println("成员变量 获取当前对象的所有方法:"+ Arrays.toString(userClass.getFields())); System.out.println("成员变量 获取当前对象的所有成员变量:"+ Arrays.toString(userClass.getDeclaredFields())); } }
利用 Class 类的对象userClass 可以访问user 对象的描述信息、User 类的信息以及基类 Object 的信息。下面列出了通过反射可以访问的信息。
| 类型 | 访问方法 | 返回值类型 | 说明 |
|---|---|---|---|
| 包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
| 类名称 | getName() | String 对象 | 获取该类的名称 |
| 继承类 | getSuperclass() | Class 对象 | 获取该类继承的类 |
| 实现接口 | getlnterfaces() | Class 型数组 | 获取该类实现的所有接口 |
| 构造方法 | getConstructors() | Constructor 型数组 | 获取所有权限为 public 的构造方法 |
| getDeclaredContruectors() | Constructor 对象 | 获取当前对象的所有构造方法 | |
| 方法 | getMethods() | Methods 型数组 | 获取所有权限为 public 的方法 |
| getDeclaredMethods() | Methods 对象 | 获取当前对象的所有方法 | |
| 成员变量 | getFields() | Field 型数组 | 获取所有权限为 public 的成员变量 |
| getDeclareFileds() | Field 对象 | 获取当前对象的所有成员变量 | |
| 内部类 | getClasses() | Class 型数组 | 获取所有权限为 public 的内部类 |
| getDeclaredClasses() | Class 型数组 | 获取所有内部类 | |
| 内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
优点:
- 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
- 与 Java 动态编译相结合,可以实现无比强大的功能。
- 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
- 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
- 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。
Java 反射机制在一般的 Java 应用开发中很少使用,即便是 Java EE 阶段也很少使用。
二、Java反射机制相关接口
实现 Java 反射机制的类都位于 java.lang.reflect 包中,java.lang.Class 类是 Java 反射机制 API 中的核心类
2.1.java.lang.Class 类
java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。Class 类提供了很多方法可以获得运行时对象的相关信息,如下:
public class Test03 { public static void main(String[] args) { // 获得Class实例 // 1.通过类型class静态变量 String str = "Hello"; // 2.通过对象的getClass()方法 Class cls2 = str.getClass(); // int 是基本数据类型,所以输出结果为 true;Integer 是类,是引用数据类型,所以输出结果为 false。 // 获得int类型Class实例 Class cls3 = int.class; // 获得Integer类型Class实例 Class cls4 = Integer.class; System.out.println("cls2类名称:" + cls2.getName()); System.out.println("cls2是否为接口:" + cls2.isInterface()); System.out.println("cls2是否为数组对象:" + cls2.isArray()); System.out.println("cls2父类名称:" + cls2.getSuperclass().getName()); System.out.println("cls2是否为基本类型:" + cls2.isPrimitive()); System.out.println("cls3是否为基本类型:" + cls3.isPrimitive()); System.out.println("cls4是否为基本类型:" + cls4.isPrimitive()); } }
结果如下
cls2类名称:java.lang.String cls2是否为接口:false cls2是否为数组对象:false cls2父类名称:java.lang.Object cls2是否为基本类型:false cls3是否为基本类型:true cls4是否为基本类型:false
2.2.java.lang.reflect 包
java.lang.reflect 包提供了反射中用到类,主要的类说明如下:
- Constructor 类:提供类的构造方法信息。
- Field 类:提供类或接口中成员变量信息。
- Method 类:提供类或接口成员方法信息。
- Array 类:提供了动态创建和访问 Java 数组的方法。
- Modifier 类:提供类和成员访问修饰符信息
public class Test04 { public static void main(String[] args) { try { //动态加载String类的运行时对象 //通过 Class 的静态方法forName(String)创建某个类的运行时对象,其中的参数是类全名字符串,如果在类路径中找不到这个类则抛出 ClassNotFoundException 异常 Class c = Class.forName("java.lang.String"); //获取成员方法集合 通过 Class 的实例方法 getDeclaredMethods() 返回某个类的成员方法对象数组。 Method[] declaredMethods = c.getDeclaredMethods(); //遍历成员方法 for (Method method : declaredMethods) { System.out.println("-----------------------------------------"); //输出权限修饰符,如public、protected、private System.out.print(Modifier.toString(method.getModifiers())); // 输出返回值类型名称 System.out.print(" " + method.getReturnType().getName() + " "); // 输出方法名称 System.out.println(method.getName() + "();"); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
2.3.获取字节码信息的四种形式
public class Test10 { public static void main(String[] args) throws ClassNotFoundException { //案例:以User的字节码信息为案例 //方式1:通过getClass()方法获取 User user = new User(); Class userClass1 = user.getClass(); System.out.println(userClass1); //方式2:通过内置class属性: Class userClass2 = User.class; System.out.println(userClass2); System.out.println(userClass1 == userClass2); //注意:方式1和方式2 不常用 //方式3:--》用的最多:调用Class类提供的静态方法forName Class userClass3 = Class.forName("com.network.program.User"); System.out.println(userClass3); //方式4:利用类的加载器(了解技能点) ClassLoader loader = Test.class.getClassLoader(); Class userClass4 = loader.loadClass("com.network.program.User"); System.out.println(userClass4); } }
三、获取构造器和创建对象
为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor 类型的对象或者数组。
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>...parameterTypes)
访问指定的构造方法,就需要根据该构造方法的入口参数的类型来访问。如下:访问一个参数类型依次为 int 和 String 类型的构造方法,下面的两种方式均可以实现。
objectClass.getDeclaredConstructor(int.class,String.class); objectClass.getDeclaredConstructor(new Class[]{int.class,String.class});
创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。如下:
| 方法名称 | 说明 |
|---|---|
| isVarArgs() | 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 false |
| getParameterTypes() | 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型 |
| getExceptionTypes() | 以 Class 数组的形式获取该构造方法可能抛出的异常类型 |
| newInstance(Object … initargs) | 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示 采用默认无参的构造方法 |
| setAccessiable(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对 象 |
| getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数 |
通过 java.lang.reflect.Modifier 类可以解析出 getMocMers() 方法的返回值所表示的修饰符信息。
| 静态方法名称 | 说明 | |
|---|---|---|
| isStatic(int mod) | 如果使用 static 修饰符修饰则返回 true,否则返回 false | |
| isPublic(int mod) | 如果使用 public 修饰符修饰则返回 true,否则返回 false | |
| isProtected(int mod) | 如果使用 protected 修饰符修饰则返回 true,否则返回 false | |
| isPrivate(int mod) | 如果使用 private 修饰符修饰则返回 true,否则返回 false | |
| isFinal(int mod) | 如果使用 final 修饰符修饰则返回 true,否则返回 false | |
| toString(int mod) | 以字符串形式返回所有修饰符 | |
如下代码判断对象 connect 对应的构造方法是否被 public 修饰,以及以字符串形式获取该构造方法的所有修饰符。
int modifiers = connect.getModifiers(); // 获取构造方法的修饰符整数 boolean isPublic = Modifier.isPublic(modifiers); // 判断修饰符整数是否为public string allModifiers = Modifier.toString(modifiers);
案例如下:调用 Constructor 类的方法获取构造方法的信息。创建一个User类
public class User implements Serializable{ private String name; private String password; public User() { } public User(String name) { this.name = name; } public User(String name, String password) { this.name = name; this.password = password; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } }
创建测试代码,通过反射访问 User类中的所有构造方法,将该构造方法是否带可变类型参数、入口参数类型和可能拋出的异常类型信息输出到控制台。
public class Test05 { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //获取动态类User Class userClass = User.class; //获取User类的所有构造方法 Constructor[] declaredConstructors = userClass.getDeclaredConstructors(); //遍历所有构造方法 for (Constructor declaredConstructor : declaredConstructors) { System.out.println("---------------------------------"); //判断构造方法的参数是否可变 System.out.println("查看是否允许带可变数量的参数:"+declaredConstructor.isVarArgs()); System.out.println("构造方法的入口参数类型依次为:"); //获取所有的参数类型: Class[] parameterTypes = declaredConstructor.getParameterTypes(); for (Class parameterType : parameterTypes) { System.out.println(parameterType+" "); } System.out.println("构造方法可能拋出的异常类型为:"); Class[] exceptionTypes = declaredConstructor.getExceptionTypes(); for (Class exceptionType : exceptionTypes) { System.out.println(exceptionType+" "); } } //获取指定的构造器: //得到空构造器 Constructor con1 = userClass.getConstructor(); System.out.println(con1); //得到两个参数的有参构造器: Constructor con2 = userClass.getConstructor(String.class, String.class); System.out.println(con2); //得到一个参数的有参构造器:并且是private修饰的 Constructor con3 = userClass.getDeclaredConstructor(String.class); System.out.println(con3); //有了构造器以后,就可以创建对象: Object o1 = con1.newInstance();//User{name='null', password='null'} System.out.println(o1); Object o2 = con2.newInstance("liming", "123456"); //User{name='liming', password='123456'} System.out.println(o2); } }
四、通过反射获取以及调用方法
要动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个 Method 类型的对象或者数组。
- getMethods()
- getMethods(String name,Class<?> …parameterTypes)
- getDeclaredMethods()
- getDeclaredMethods(String name,Class<?>...parameterTypes)
如果是访问指定的构造方法,需要根据该方法的入口参数的类型来访问。例如,访问一个名称为 max,入口参数类型依次为 int 和 String 类型的方法。
objectClass.getDeclaredConstructor("max",int.class,String.class);
objectClass.getDeclaredConstructor("max",new Class[]{int.class,String.class});
Method 类的常用方法如下:
| 静态方法名称 | 说明 |
|---|---|
| getName() | 获取该方法的名称 |
| getParameterType() | 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型 |
| getReturnType() | 以 Class 对象的形式获得该方法的返回值类型 |
| getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型 |
| invoke(Object obj,Object...args) | 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型 |
| isVarArgs() | 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false |
| getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
获取之前User类中的方法信息
public class Test06 { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { //获取动态类User User user = new User("amdin","123321"); Class userClass = user.getClass(); //获取User类的所有构造方法 Method[] declaredMethods = userClass.getDeclaredMethods(); //遍历所有构造方法 for (Method declaredMethod : declaredMethods){ System.out.println("---------------------------------"); System.out.println("方法的名称:"+declaredMethod.getName()); System.out.println("方法是否带有可变数量的参数:"+declaredMethod.isVarArgs()); //获取所有的参数类型: Class[] parameterTypes = declaredMethod.getParameterTypes(); for (Class parameterType : parameterTypes) { System.out.println(parameterType+" "); } System.out.println("方法可能拋出的异常类型为:"); Class[] exceptionTypes = declaredMethod.getExceptionTypes(); for (Class exceptionType : exceptionTypes) { System.out.println(exceptionType+" "); } } System.out.println("----------------通过反射调用方法------------------"); //创建对象 User user1 = new User("admin", "123321"); // 通过反射调用User类的toString方法 //类对象的getMethod方法中,第一个参数为字符串类型,是要调用的实例方法的名称,后续的参数为要调用方法的参数类型的类对象。 Method method2 = userClass.getMethod("toString"); //Method对象的invoke方法,第一个参数为要调用方法所在类的实例对象,后续的参数为要调用的方法的参数。 System.out.println(method2.invoke(user1)); } }
五、获取属性和对属性进行赋值
通过下列任意一个方法访问成员变量(属性)时将返回 Field 类型的对象或数组。
- getFields()
- getField(String name)
- getDeclaredFields()
- getDeclaredField(String name)
要访问一个名称为 price 的成员变量,示例代码如下:
object.getDeciaredField("price");
| 方法名称 | 说明 |
|---|---|
| getName() | 获得该成员变量的名称 |
| getType() | 获取表示该成员变量的 Class 对象 |
| get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 类型 |
| set(Object obj, Object value) | 将指定对象 obj 中成员变量的值设置为 value |
| getlnt(0bject obj) | 获得指定对象 obj 中成员类型为 int 的成员变量的值 |
| setlnt(0bject obj, int i) | 将指定对象 obj 中成员变量的值设置为 i |
| setFloat(Object obj, float f) | 将指定对象 obj 中成员变量的值设置为 f |
| getBoolean(Object obj) | 获得指定对象 obj 中成员类型为 boolean 的成员变量的值 |
| setBoolean(Object obj, boolean b) | 将指定对象 obj 中成员变量的值设置为 b |
| getFloat(Object obj) | 获得指定对象 obj 中成员类型为 float 的成员变量的值 |
| setAccessible(boolean flag) | 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量 |
| getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
为了演示方便,修改User类如下:
public class User implements Serializable{ private String name; private String password; public String sex; public String address; public User() { } public User(String name, String password) { this.name = name; this.password = password; } public User(String name, String password, String sex, String address) { this.name = name; this.password = password; this.sex = sex; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", password='" + password + '\'' + ", sex='" + sex + '\'' + ", address='" + address + '\'' + '}'; } }
测试类如下:
public class Test08 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException { //获取运行时类的字节码信息 Class userClass = User.class; System.out.println("--------------------------------"); //获取属性 getFields:获取运行时类和父类中被public修饰的属性 Field[] fields = userClass.getFields(); System.out.println("获取运行时类和父类中被public修饰的属性:"); for (Field field : fields) { System.out.println(field); } //getDeclaredFields:获取运行时类中的所有属性 System.out.println("--------------------------------"); System.out.println("获取运行时类中的所有属性:"); Field[] declaredFields = userClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } //获取指定的属性address System.out.println("--------------------------------"); System.out.println("获取指定的属性address: "); Field address = userClass.getField("address"); System.out.println(address); //某个属性的获取修饰符 1.代表public 2.private System.out.println("--------------------------------"); System.out.println("获取指定的属性address的修饰符: "); int modifiers = address.getModifiers(); System.out.println(modifiers); System.out.println("获取指定的属性name的修饰符: "); Field name = userClass.getDeclaredField("name");//由于name被private修饰所以用getDeclaredField获取 int modifiers1 = name.getModifiers(); System.out.println(modifiers1); //获取属性的数据类型: System.out.println("--------------------------------"); Class clazz = address.getType(); System.out.println(clazz.getName()); //获取属性的名字: System.out.println("--------------------------------"); System.out.println(address.getName()); System.out.println("--------------------------------"); //给属性赋值,必须有对象 Field address1 = userClass.getField("address");//获取指定的属性address Field sex = userClass.getField("sex");//获取指定的属性age Field uname = userClass.getDeclaredField("name");//获取指定的属性name Field pwd = userClass.getDeclaredField("password");//获取指定的属性password //通过反射操作类的私有(private)成员变量时,需要通过field.setAccessible(true)将字段设置为可以访问的。 uname.setAccessible(true); pwd.setAccessible(true); //创建对象 Object obj = userClass.newInstance(); //设置值 address1.set(obj,"河南省三门峡市区");//给obj这个对象的address属性设置具体的值,"河南省三门峡市区" sex.set(obj,"男"); uname.set(obj,"liming"); pwd.set(obj,"2342143"); System.out.println(obj); } }
六、通过反射获取类的接口,所在包,注解
还是以User类为案例演示如下:
public class Test09 { public static void main(String[] args) { //获取字节码信息: Class cls = User.class; System.out.println("--------------------------------"); //获取运行时类的接口: Class[] interfaces = cls.getInterfaces(); for(Class c:interfaces){ System.out.println(c); } System.out.println("--------------------------------"); //得到父类的接口: //先得到父类的字节码信息: Class superclass = cls.getSuperclass(); //得到接口: Class[] interfaces1 = superclass.getInterfaces(); for(Class c:interfaces1){ System.out.println(c); } System.out.println("--------------------------------"); //获取运行时类所在的包: Package aPackage = cls.getPackage(); System.out.println(aPackage); System.out.println(aPackage.getName()); System.out.println("--------------------------------"); //获取运行类的注解: Annotation[] annotations = cls.getAnnotations(); for(Annotation a:annotations){ System.out.println(a); } } }

浙公网安备 33010602011771号