U 需要知道的反射知识 – 大白话
1. 反射前言
.java类文件会被编译为.class二进制流文件,类加载器将其加载进入内存中,JVM堆内存中会生成一个Class对象,一个类对应于一个Class对象,通过Class对象可以获得其在方法区中存储的类完整结构信息,包括字段,方法等属性
2. 何为反射
- 反射 :程序在运行期间,可以通过
java反射机制,动态获取某个类的结构信息(属性,方法等)- 动态 : 程序运行期间,无法修改源代码
- 静态 :通过
new创建对象,然后调用某个类的字段或者方法,明确知道要使用的某个确定的类
- 总结 :通过反射机制,就能在程序运行期间,动态(用户自定义规则)去获取某个类的字段方法等属性
3.反射流程
以调用 Person 类中 public get( ) 为例
- 正常方式 :
import Person类包名- 通过
new关键字创建Person对象p - 对象调用方法
p.get( )
- 反射方式 :
- 通过
Person全类名获取Class类对象 - 通过
Class对象 就可以得到访问当前Person类的get( )
- 通过
4. 为什么要使用反射
-
反射基本是许多框架中使用到的技术,框架需要兼容每个使用者的要求,降低代码耦合度
-
反射提供动态处理,可以实现按需加载,有效减少不必要的类加载到内存
例如:
JDBC获取数据库连接参数的方式 (见下 :实例讲解) -
后续示例中使用到的类
- 注解类
@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.LOCAL_VARIABLE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value(); }@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Test { String value(); }- Action 接口
interface Action{ void run(); public final String food = "肉"; //jdk11之后允许定义私有方法 private void privateEate(){System.out.println("Action --> privateEate()");} private static void privateShow(){System.out.println("Action --> privateShow()");} public static void publicShow(){System.out.println("Action --> publicShow()");} }- World 类
@Test(value = "world - Test") @MyAnnotation(value = "world - MyAnnotation") public class World { @MyAnnotation(value = "type") private String type; @MyAnnotation(value = "year") public int year; @Test(value = "origin") protected String origin; private String getType(){return type;}; public int getYear(){return year;} protected String getOrigin(){return origin;} }- Person 类
import java.util.ArrayList; @MyAnnotation(value = "person-MyAnnotation") @Test(value = "person Test") class Person extends World implements Action{ @MyAnnotation(value = "name") private String name; @Test(value = "sex") protected String sex; @MyAnnotation(value = "age") public int age = 20; private ArrayList<String> toys = new ArrayList<>(); public Person(String name, int age) { this.name = name;this.age = age;System.out.println("构造方法:Person(String name, int age)");} public Person(String name) { this.name = name;System.out.println("构造方法:Person(String name)");} public Person() {System.out.println("构造方法:Person()");} @MyAnnotation(value = "getName") private String getName(String name) {return name;} public String getNamePblic() {return name;} @MyAnnotation(value = "setName") public void setName(String name) {this.name = name;} protected int getAge() { return age;} public void setAge(int age) {this.age = age;} private void setAgePrivate(int age) {this.age = age;} public static void eat(String some){System.out.println("eat "+some);} @Override public void run() {System.out.println("run()");}; public ArrayList<String> getToys(){toys.add("钢铁侠");toys.add("蜘蛛侠");return toys;} @Override public String toString() {return "Person [age=" + age + ", name=" + name + ", toys=" + toys + "]";} }
5. 通过反射如何获取 Class 对象
-
通过已知的具体类调用
.class -
Class p1 = Person.class; // 使用泛型方式在获取Person对象时会自动转化,不需要强转 Class<Person> p2 = Person.class; -
通过 对象 调用
getClass()方法获取Person person = new Person("王五"); Class<? extends Person> class1 = person.getClass();注释:泛型通配符
<? extends Person>:表示泛型必须是Person类或者其子类,固定上限<? super Person>:表示泛型必须是Person类或者其父类,固定下限
-
通过
Class.forName(全类名)Class<?> forName = Class.forName("com.qin.Person"); -
通过类加载器来获取
Class<?> loadClass = Person.class.getClassLoader().loadClass("com.qin.Person");
6. 反射获取实例对象
-
通过
Class对象获得当前类的构造器,然后通过构造器创建对象 -
通过反射创建类对象时,该类必须提供构造方法
Class对象调用 newInstance(),该方法使用时需要提供Person类的无参构造方法,否则会抛出异常 ,jdk9之后不推荐使用
Class<?> clazz = Class.forName("com.qin.Person"); Person person1= (Person) clazz.newInstance(); System.out.println(person1.toString()); //执行结果:Person [age=0, name=null, toys=[]]Constructor构造器调用newInstance()方法,推荐使用,可以指定创建对象时,调用指定Person类的构造方法
//第一个参数表示构造方法的参数类型,第二个参数是实例化的值 Person person2 = (Person) clazz.getDeclaredConstructor(String.class).newInstance("王五"); Person person3 = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18); System.out.println(person2.toString()); System.out.println(person3.toString()); //执行结果: //构造方法:Person() //Person [age=20, name=null, toys=[]] //构造方法:Person(String name) //构造方法:Person(String name, int age) //Person [age=20, name=王五, toys=[]] //Person [age=18, name=王五, toys=[]]
7. 获取类字段属性
-
getField ( String name ):通过指定名称获取某个特定修饰符为public的字段Class<?> clazz = Class.forName("com.qin.Person"); Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18); //通过指定属性名获取 public 类型字段 Field age = clazz.getField("age"); //通过指定属性名获取 任意 类型字段 Field name = clazz.getDeclaredField("name"); //获取字段名称 System.out.println(age.getName()); //获取字段类型 System.out.println(age.getType()); //通过person对象获取字段的值 System.out.println(age.get(person)); -
获取所有修饰符为的
public字段属性,包括父类中的字段//获取所有的public字段属性,包括父类中的字段,包括接口中的字段 Field[] fields1 = clazz.getFields(); for (Field field1 : fields1) { System.out.println(field1.getName()); } //执行结果: //age //food //year -
获取当前类中的所有修饰符的字段包括
private//获取当前类中的所有属性的字段包括private Field[] fields2 = clazz.getDeclaredFields(); for (Field field2 : fields2) { System.out.println(field2.getName()); } //执行结果: //name //sex //age //toys
8.获取类方法属性
-
getMethod( "方法名", 类型名.class):通过方法名和返回值获取修饰符为public方法属性Method method1 = clazz.getMethod("setName", String.class); -
getDeclaredMethod( "方法名", 类型名.class):通过方法名和返回值获取所有修饰符的方法属性//第二个参数表示返回值类型,void 可传入 null Method method2 = clazz.getDeclaredMethod("getAge", null); -
getMethods():通过方法名获取当前类,所有父类中修饰符为public的方法,不包括接口中的pulbic方法Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); } -
getDeclaredMethods():获取当前类所有的方法,不包括构造方法Method[] methods1 = clazz.getDeclaredMethods(); for (Method method1 : methods1) { System.out.println(method1); }
9. Class 类方法操作说明
-
通过反射获得的
Class对象,可以获取类中的 字段,方法,类型,构造方法,修饰符,泛型,注解等信息 -
Class类大多数方法 包含Declared与 不包含Declared的区别:-
如果方法名中含有
Declared(比如:getDeclaredMethods(String name))表示获取类中的任意修饰符为的方法 -
如果方法名中没有
Declared表示只能获取修饰符为public的方法getMethods()这类返回Method数组的不表示 返回当前类中的所有public方法,而是所有类,包括父类中修饰符为public的方法都理解为不包含
Declared只返回public修饰符的结构属性
-
10.获取注解信息
-
获取对象上所有的注解信息,无法获取继承下来的注解
Annotation[] annotations = clazz.getAnnotations(); Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation.toString()); } -
只有注解被标注为
@Retention(RetentionPolicy.RUNTIME)的才能获取到该注解RetentionPolicy.RUNTIME:该注解将会一直保留到运行时runtimeRetentionPolicy.SOURCE:该注解只保留在源文件中,类似于标记作用RetentionPolicy.CLASS:该注解只保留到class文件中,也就是随着编译器编译到字节码中,运行时反射不对解析改注解
11.获取泛型信息
-
如何获取泛型信息
-
首先获得
Method对象来获取方法上面的泛型 -
getGenericParameterTypes()获取方法参数集合 -
如果参数类型为
ParameterizedType类型,需要强转为ParameterizedType类 -
然后获取方法所有参数的实际类型
getActualTypeArguments() -
通过调用
Type类的getTypeName()来获取名称//1、通过全类名获取class对象 Class<?> clazz = Class.forName("com.qin.Person"); //2、通过指定方法名获取名字为 setToys 的方法 Method method = clazz.getDeclaredMethod("setToys",ArrayList.class); //3、获取方法的所有参数 Type[] types = method.getGenericParameterTypes(); //3、循环遍历方法参数 for (Type type : types) { //4、如果参数是属于 ParameterizedType 类型 if(type instanceof ParameterizedType){ //5、获取实际的泛型参数类型 Type[] actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments(); //6、循环遍历获取泛型类型 for (Type aType : actualTypeArguments) { System.out.println(aType.getTypeName()); if(aType instanceof ParameterizedType){ //7、获取实际的泛型参数类型 Type[] actualTypeArguments1 = ((ParameterizedType)aType).getActualTypeArguments(); //8、循环遍历获取泛型类型 for (Type aType1 : actualTypeArguments1) { System.out.println(aType1.getTypeName()); } } } } }
-
12.获取字段以及方法调用操作实践
-
如何通过反射获取某个类中的字段,以及如何重置字段中的值
-
全类名获取
class对象 -
通过
Constructor中的newInstance()创建Person对象 -
根据字段修饰符执行不同的操作
-
如果字段修饰符为
Public,可以使用getField()或者getDeclaredField()来获取字段 -
如果字段修饰符不为
Public,则需要调用getDeclaredField()来获取字段-
同时需要调用
setAccessible(true)设置接入访问 -
否则会抛出如下异常:
Exception in thread "main" java.lang.IllegalAccessException: class com.qin.App cannot access a member of class com.qin.Person with modifiers "private"
-
-
-
调用
get()方法,传入类对象获取属性值 -
调用
set()方法,传入类对象以及需实际值来设置属性的值
//1、通过全类名获取 class 对象 Class<?> clazz = Class.forName("com.qin.Person"); //2、通过Constructor创建 Person 对象 Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18); //3、通过方法名获取方法:name 修饰符为 private Field name = clazz.getDeclaredField("name"); //4、调用 get() 方法,传入类对象 获取属性值 String str = (String) name.get(person); System.out.println(str); //5、设置接入访问为 true name.setAccessible(true); //6、调用 set(),修改属性值 name.set(person, "李四"); System.out.println(person.getNamePblic()); -
-
如何通过反射调用类中的方法
-
全类名获取
class对象 -
通过
Constructor中的newInstance()创建Person对象 -
根据方法修饰符执行不同的操作
-
如果调用的方法修饰符为
Public,可以使用getMethod()或者getDeclaredMethod()来获取方法结构Method -
如果调用的方法修饰符不为
Public,则需要调用getDeclaredMethod()来获取方法-
同时需要调用
setAccessible(true)设置接入访问 -
否则会抛出如下异常:
Exception in thread "main" java.lang.IllegalAccessException: class com.qin.App cannot access a member of class com.qin.Person with modifiers "private"
-
-
-
调用
invoke()方法,传入对应类对象,以及参数值 -
invoke()的返回值即是类中该方法的返回值
//1、通过全类名获取class对象 Class<?> clazz = Class.forName("com.qin.Person"); //2、通过Constructor(参数类型)中的newInstance(实参数值)创建 Person对象 Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18); //3、通过方法名获取方法:setName为public 修饰符 Method setName = clazz.getMethod("setName", String.class); //4、调用方法,传入类对象,以及参数值 setName.invoke(person, "张三"); System.out.println(person.getNamePblic()); //3、通过方法名获取方法:setAgePrivate 为私有方法 Method setAgePrivate = clazz.getDeclaredMethod("setAgePrivate", int.class); //4、设置允许接入访问 setAgePrivate.setAccessible(true); //5、、调用方法,传入类对象,以及参数值 setAgePrivate.invoke(person, 50); System.out.println(person.getAge()); -
13.实例解析
-
动态代理
-
Proxy:完成代理的操作类,是所有动态代理类的父类 -
通过其静态方法
static Object newProxyInstance(ClassLoader loader, Class<?>...interface, InvocationHandler h)直接创建一个动态代理对象 -
实现动态代理的步骤:
- 创建被代理的类,实现相应功能接口
- 实现
InvocationHandler接口,并重写其invoke(Object proxy, Method method, Object[] args)方法- 方法内部通过
method.invoke(obj, args)直接调用invoke()方法实现被代理类的方法调用 Object obj参数可通过实现类构造方法传入或者提供一个赋值的方法
- 方法内部通过
- 通过
Proxy.newProxyInstance()创建代理对象转为接口,多态 - 通过代理对象调用被代理类的方法
public class App { public static void main(String[] args) throws Exception{ Action action = (Action) ProxyFactory.getProxyObject(new Person("王老五",30)); action.run(); } } class ProxyFactory{ public static Object getProxyObject(Object object){ MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(object); return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), handler); } } class MyInvocationHandler implements InvocationHandler{ private Object obj; public void bind(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(obj, args); } }
-
这篇博客详细介绍了Java反射机制,包括反射的前言、概念、流程、使用原因、获取对象、实例化对象、访问字段和方法、处理注解和泛型信息。通过反射,可以在运行时动态获取类的结构信息,降低代码耦合度,适用于框架开发和动态处理场景。
837

被折叠的 条评论
为什么被折叠?



