目录
一. 概述
Java 反射机制是Java语言的一个特性,它允许程序在运行时访问、检查和修改它自己的结构,例如类、接口、字段(成员变量)和方法。反射机制使得Java程序能够在运行时动态地创建对象、调用方法、改变字段值等,而不需要在编译时就知道具体的类信息。
说人话:就是通过Class字节码文件获取对象信息、成员变量、构造、方法,还可以获取到父类中方法,私有方法等。
二. 反射的特点
动态性: 反射允许程序在运行时加载、探查、使用编译期间完全未知的
.class
文件。通用性: 反射提供了一种通用的方法来处理对象,不依赖于具体的类。
扩展性: 反射允许程序动态地扩展,可以处理在编译时未知的类。
内省: 反射提供了一种方式来获取类和对象的信息,如字段、方法、构造器等。
三. 反射主要应用
创建灵活的代码: 可以使用反射动态地创建对象和调用方法,使代码更加灵活。
开发框架: 许多Java框架(如Spring、Hibernate)使用反射来实现依赖注入、代理模式等功能。
测试工具: JUnit 等测试框架使用反射来动态地调用测试方法。
动态代理: Java 提供了
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现动态代理。访问私有成员: 通过反射可以访问和修改私有字段和方法。
四. 反射注意事项
性能开销: 反射操作通常比直接的Java代码操作要慢,因为它们需要额外的解析步骤。
安全限制: 反射可能破坏封装性,因此需要确保代码的安全性。
异常处理: 反射操作可能会抛出多种异常,如
ClassNotFoundException
、NoSuchMethodException
、IllegalAccessException
等(也可通过反射机制获取方法抛出的异常信息)。
五. 代码示例详解
1. 解析图:
通过Class字节码对象可以获取:
- 获取构造方法对象
- 获取构造权限修饰符(public,protected,private)
- 获取构造函数名称
- 获取形参
- 创建对象 newInstance()
- 获取字段(成员变量)
- 获取字段权限修饰符(public,protected,private)
- 获取字段名称
- 获取参数类型
- 设置值/获取值
- 获取成员方法
- 获取方法修饰符(public,protected,private)
- 获取方法名称
- 获取形参
- 运行方法
2. 获取字节码对象
通过反射机制获取对象信息需要先获取 Class 字节码对象,获取Class字节码对象可以通过三种方式获取:
- 通过全限定名:Class 类中的静态方法 forName(全类名)
- 通过类.class:类名称.class
- 通过对象中的方法:对象中getClass()方法获取字节码对象
package com.demo.reflect; /** * 文件名:Mamin * 创建者: * 创建时间:2024-09-09 * 描述:获取class字节码对象三种方式 */ public class Main { public static void main(String[] args) throws ClassNotFoundException { //1. 通过权限定名获取字节码文件(常用方式) Class forClass = Class.forName("com.demo.reflect.User"); //2. 通过类名获取class字节码对象(多使用在参数中) Class userClass = User.class; //3. 通过对象获取class字节码对象(通过对象获取字节码对象时使用) User user = new User(); Class obectUserClass = user.getClass(); //因为class字节码文件在硬盘中是唯一的,所以说三种方法获得的class对象是同一个 if((forClass == userClass) && (forClass == obectUserClass)){ System.out.println("同一个对象"); } } }
3. 通过反射获取构造函数并创建对象
3.1. Class类中用于获取构造方法的方法
//返回所有公共构造方法对象的数组
Constructor<?>[] getConstructors();
//返回所有构造方法对象的数组
Constructor<?>[] getDeclaredConstructors();
//返回单个公共构造方法对象
Constructor<T> getConstructor(Class<?>... parameterTypes);
//返回单个构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>.. parameterTypes)
3.2. Constructor类中用于创建对象的方法
//根据指定的构造方法创建对象
Constructor类中用于创建对象的方法 T newInstance(Object...initargs);
//设置为true,表示取消访问检查(私有修饰符必须设置这个)
setAccessible(boolean flag);
3.3. 代码
- 创建User用户类
package com.demo.reflect; /** * 文件名:User * 创建者: * 创建时间:2024-09-09 * 描述:自定义用户信息类 */ public class User { private String name; //名称 private int age; //年龄 public boolean sex; // 性别 public User() { } public User(String name) { this.name = name; } protected User(int age) { this.age = age; } private User(boolean sex) { this.sex = sex; } 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; } public boolean isSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + '}'; } }
- 创建测试类
package com.demo.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Parameter; /** * 文件名:Demo01 * 创建者: * 创建时间:2024-09-09 * 描述: */ public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //1.通过权限定名获取class字节码对象 Class clazz = Class.forName("com.demo.reflect.User"); //2.获取单个 private 修饰的有参构造 Constructor privateCon = clazz.getDeclaredConstructor(String.class); privateCon.setAccessible(true); //如果修饰符为私有的必须设置为true,表示取消访问检查 System.out.println("获取单个 private 私有构造:"+privateCon); //3.通过 newInstance() 方法创建User对象,参数是构造里的参数 User user = (User) privateCon.newInstance("码农"); System.out.println(user); //4.获取修饰符 private 返回的是int类型,这个jdk定义的常量,可在jdk文档中查看 int pri = privateCon.getModifiers(); System.out.println(pri); //5.获取构造参数 Parameter[] pars = privateCon.getParameters(); for (Parameter par : pars) { System.out.println(par); } System.out.println("===================下面都是获取构造对象,其他都一样============================="); Constructor[] pubCon = clazz.getConstructors(); for(Constructor con : pubCon){ System.out.println("获取所有public修饰的构造方法 : "+con); } //获取 public,private,protected 修饰的所有构造 Constructor[] cons = clazz.getDeclaredConstructors(); for(Constructor c : cons){ System.out.println("获取所有构造包括 private 和 protected : "+c); } //获取单个 private 修饰的有参构造 Constructor privCon = clazz.getDeclaredConstructor(boolean.class); System.out.println("获取单个 private 私有构造:"+privCon); } }
- 测试结果
4. 通过反射获取对象字段信息
4.1. Class类中获取字段属性信息的方法
//返回所有公共成员变量对象的数组
Field[] getFields();
//返回所有成员变量对象的数组
Field[] getDeclaredFields();
//返回单个公共成员变量对象
Field getField(String name);
返回单个成员变量对象
Field getDeclaredField(String name);
4.2. Field类中创建对象的方法
//赋值
void set(Object obj,Object value);
//获取值
Object get(Object obj);
4.3. 代码
- 测试类
package com.demo.reflect; import java.lang.reflect.Field; /** * 文件名:Demo02 * 创建者: * 创建时间:2024-09-09 * 描述:通过反射获取成员变量测试类 */ public class Demo02 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //1.通过权限定名获取class字节码对象 Class clazz = Class.forName("com.demo.reflect.User"); //2.获取公开的所有属性 Field[] fields = clazz.getFields(); for (Field field : fields) { System.out.println("获取public修饰的属性:"+field); } //3.获取所有的的属性,包括私有的属性 Field[] fields1 = clazz.getDeclaredFields(); for (Field field : fields1) { System.out.println("获取public、private、protected修饰的属性:"+field); } //4.获取单个属性私有修饰也可获取 Field name = clazz.getDeclaredField("name"); name.setAccessible(true); //如果修饰符为私有的必须设置为true,表示取消访问检查 System.out.println("获取单个属性:"+name); //5.获取属性名称 String cName = name.getName(); System.out.println("获取属性名称:"+cName); //6.获取属性修饰符 int pr = name.getModifiers(); System.out.println("获取修饰符:"+pr); //7.获取成员变量的类型 Class cType = name.getType(); System.out.println("获取成员变量的类型:"+cType); //8.获取属性的值 User user = new User(); user.setName("测试"); Object userName = name.get(user); System.out.println("获取对象的值:"+userName); //9.通过反射修改对象的值 name.set(user,"张三"); // 如果字段修饰符是私有修饰符需要设置name.setAccessible(true),取消访问检查 System.out.println("修改对象值:"+user); } }
- 测试结果
5. 通过反射获取方法并执行
5.1. 利用Class字节码对象通过反射获取成员方法对象
//返回所有公共成员方法对象的数组,包括继承的
Method[] getMethods();
//返回所有成员方法对象的数组,不包括继承的
Method[] getDeclaredMethods();
//返回单个公共成员方法对象
Method getMethod(String name, Class<?>.. parameterTypes);
//返回单个成员方法对象
Method getDeclaredMethod(String name, Class<?>..parameterTypes);
5.2. 创建方法对象并执行方法
//Method类中用于创建对象的方法
//参数一:用obj对象调用该方法
//参数二:调用方法的传递的参数(如果没有就不写)
//返回值:方法的返回值(如果没有就不写)
Object invoke(Object obj, Object... args);//运行方法
5.3. 代码
- 在User类中添加新成员方法
package com.demo.reflect; import java.io.IOException; /** * 文件名:User * 创建者: * 创建时间:2024-09-09 * 描述:自定义用户信息类 */ public class User { private String name; //名称 private int age; //年龄 public boolean sex; // 性别 public User() {} public User(String name) { this.name = name; } protected User(int age) { this.age = age; } private User(boolean sex) { this.sex = sex; } 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; } public boolean isSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } private String eat(String name)throws IOException { return name+"在吃饭"; } private void game(String name){ System.out.println(name+"在打游戏"); } public String work(String name){ System.out.println(name+"在工作"); return "小明工作很忙"; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + '}'; } }
- 测试类型
package com.demo.reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; /** * 文件名:Demo03 * 创建者: * 创建时间:2024-09-10 * 描述:通过反射获取对象成员方法测试类 */ public class Demo03 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { //1.通过权限定名获取class字节码对象 Class clazz = Class.forName("com.demo.reflect.User"); //2.获取对象方法信息(这个返回的方法会包含父类公开的方法) Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); } //3.获取当前对象的方法包括私有方法(这个不会返回父类的方法) Method[] methods1= clazz.getDeclaredMethods(); for (Method method : methods1) { System.out.println(method); } //4.获取指定单个私有的方法 Method method = clazz.getDeclaredMethod("eat", String.class); System.out.println("获取指定单个私有的方法:"+method); System.out.println("方法修饰符:"+method.getModifiers());//获取方法的修饰符 System.out.println("方法名字:"+method.getName());//获取方法的名字 System.out.println("参数个数:"+method.getParameterCount());//获取参数个数 Parameter[] parameters = method.getParameters(); //获取方法的参数 for (Parameter parameter : parameters) { System.out.println("方法参数:"+parameter); } Class[] ex = method.getExceptionTypes(); //获取方法的抛出的异常 for (Class aClass : ex) { System.out.println("异常信息"+aClass); } //5.获取指定单个方法(包括私有)并运行某个方法 Method met = clazz.getDeclaredMethod("game",String.class); met.setAccessible(true);如果修饰符为私有的必须设置为true,表示取消访问检查 //运行方法 //参数一:用obj对象调用该方法 //参数二:调用方法的传递的参数(如果没有就不写) //返回值:方法的返回值(如果没有就不写) User user = new User(); met.invoke(user,"小明"); //6.获取公开的单个有参方法并运行,打印返回值 Method method1 = clazz.getMethod("work", String.class); Object res = method1.invoke(user,"小明"); System.out.println("返回值:"+res); } }
- 测试结果
6. 通过反射获取注解信息
6.1. 在java中可以通过反射的方式获取类、字段、方法、方法参数上的注解,java中的注解有广发的应用,特别是在框架开发上,基于注解的实现框架多不胜数。
注解获取主要从:
- 获取java类注解
- 获取对象属性上注解
- 获取方法上注解
- 获取方法参数注解
6.2. 代码实现
- 自定义注解(类注解、字段注解、方法注解、方法参数注解)
package com.demo.reflect; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * 文件名:MyAnnotation * 创建者: * 创建时间:2024-09-10 * 描述: 自己定义一个类上使用的注解 */ @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotationCalss { String value(); }
package com.demo.reflect; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * 文件名:MyAnnotationField * 创建者: * 创建时间:2024-09-10 * 描述:自定义一个字段上使用的注解 */ @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotationField { String value(); }
package com.demo.reflect; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * 文件名:MyAnnotationMethod * 创建者: * 创建时间:2024-09-10 * 描述: 自定义一个方法上使用的注解 */ @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotationMethod { String value(); }
package com.demo.reflect; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * 文件名:MyAnnotationCalssParameter * 创建者: * 创建时间:2024-09-10 * 描述:自定义一个方法参数上使用的注解 */ @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotationParameter { String value(); }
- 定义普通 java 类
package com.demo.reflect; /** * 文件名:MyClass * 创建者: * 创建时间:2024-09-10 * 描述:自定义类,在类上使用自己定义的 @MyAnnotation */ @MyAnnotationCalss(value = "类注解") public class MyClass { @MyAnnotationField(value = "Field-name") private String name; @MyAnnotationField(value = "Field-age") private int age; @MyAnnotationMethod(value = "Method-game") private void game(String name){ System.out.println(name+"在打游戏"); } @MyAnnotationMethod(value = "Method-work") public String work(@MyAnnotationParameter(value = "Parameter-name") String name){ System.out.println(name+"在工作"); return "小明工作很忙"; } 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; } }
- 定义测试类
package com.demo.reflect; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 文件名:AnnotationTest * 创建者: * 创建时间:2024-09-10 * 描述:通过反射获取 类 属性 方法 方法中参数 的注解测试类 */ public class AnnotationTest { public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException { Class<MyClass> clazz = MyClass.class; //1.获取类上带有 @MyAnnotationCalss 注解的实例(这个获取的是多个注解) Annotation[] myAnnotations = clazz.getAnnotations(); for (Annotation annotation : myAnnotations) { //多个注解需要判断注解类型 if(annotation instanceof MyAnnotationCalss){ MyAnnotationCalss myAnnotat =((MyAnnotationCalss) annotation); System.out.println("获取类上注解的值:"+myAnnotat.value()); } } //2.获取类上带有 @MyAnnotationCalss 注解的实例(单个获取,需要传入注解class对象) MyAnnotationCalss myAnnotation = clazz.getAnnotation(MyAnnotationCalss.class); System.out.println("获取类上注解的值:"+myAnnotation.value()); //3.获取方法上带有 @MyAnnotationMethod 注解的实例 Method method = clazz.getDeclaredMethod("work",String.class); MyAnnotationMethod annotation = method.getAnnotation(MyAnnotationMethod.class); System.out.println("获取方法上注解的值:"+annotation.value()); //4.获取方法参数带有 @MyAnnotationParameter 注解的实例(这个返回的是一个二维数组) Annotation[][] annotations = method.getParameterAnnotations(); for (Annotation[] annotation1 : annotations) { for (Annotation annotation2 : annotation1) { MyAnnotationParameter myAnnotationParameter = (MyAnnotationParameter) annotation2; System.out.println("获取参数注解的值:"+myAnnotationParameter.value()); } } //4.获取字段上带有 MyAnnotationField 注解的实例 (获取多个注解) Field field = clazz.getDeclaredField("name"); Annotation[] annots = field.getAnnotations(); for (Annotation annot : annots) { if(annot instanceof MyAnnotationField){ System.out.println("字段上的注解:"+annot); } } //5.获取字段上带有 MyAnnotationField 注解的实例(获取单个注解) MyAnnotationField myAnnotationField = field.getAnnotation(MyAnnotationField.class); System.out.println("获取属性注解的值:"+myAnnotationField.value()); } }
- 测试结果
六. 总结
1. 获取Class字节码对象方式
//1. 通过权限定名获取字节码文件(常用方式)
Class forClass = Class.forName("com.demo.reflect.User");
//2. 通过类名获取class字节码对象(多使用在参数中)
Class userClass = User.class;
//3. 通过对象获取class字节码对象(通过对象获取字节码对象时使用)
User user = new User();
Class obectUserClass = user.getClass();
2. 利用反射获取构造函数对象
//1.返回所有公共构造方法对象的数组
Constructor<?>[] getConstructors();
//2.返回所有构造方法对象的数组
Constructor<?>[] getDeclaredConstructors();
//3.返回单个公共构造方法对象
Constructor<T> getConstructor(Class<?>... parameterTypes);
//4.返回单个构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>.. parameterTypes)
//Constructor类中用于创建对象的方法
//根据指定的构造方法创建对象
T newInstance(Object...initargs);
//设置为true,表示取消访问检查(私有修饰符必须设置这个)
setAccessible(boolean flag);
3. 利用反射获取对象字段信息
//1.返回所有公共成员变量对象的数组
Field[] getFields();
//2.返回所有成员变量对象的数组
Field[] getDeclaredFields();
//3.返回单个公共成员变量对象
Field getField(String name);
//4.返回单个成员变量对象
Field getDeclaredField(String name);
//赋值
void set(Object obj,Object value);
//获取值
Object get(Object obj);
4. 利用反射获取方法并执行
//1.返回所有公共成员方法对象的数组,包括继承的
Method[] getMethods();
//2.返回所有成员方法对象的数组,不包括继承的
Method[] getDeclaredMethods();
//3.返回单个公共成员方法对象
Method getMethod(String name, Class<?>.. parameterTypes);
//4.返回单个成员方法对象
Method getDeclaredMethod(String name, Class<?>..parameterTypes);
//Method类中用于创建对象的方法
//参数一:用obj对象调用该方法
//参数二:调用方法的传递的参数(如果没有就不写)
//返回值:方法的返回值(如果没有就不写)
Object invoke(Object obj, Object... args);//运行方法
5. 利用反射获取类、字段、方法、方法参数中的注解
//1.获取类上多个注解(返回一个数组)
Annotation[] myAnnotations = clazz.getAnnotations();
//2.获取类上单个注解
Annotation annotation = clazz.getAnnotation(MyAnnotationCalss.class);
//3.获取方法上的注解
MyAnnotationMethod annotation = method.getAnnotation(MyAnnotationMethod.class);
//4.获取方法参数上的注解(返回的是二维数组)
Annotation[][] annotations = method.getParameterAnnotations();
//5.获取字段上的注解(返回多个注解)
Annotation[] annots = field.getAnnotations();
//6.获取字段上的注解(返回单个注解)
MyAnnotationField myAnnotationField = field.getAnnotation(MyAnnotationField.class);