反射
代码举例:
Class cls = Class.forName("..."); //获取具体类的字节码信息
Object o = cls.newInstance(); //创建类对象
Method method = cls.getMethod("...") //获取类对象的方法
method.invoke(o); //使用该方法
1、定义
Java反射机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java的反射机制。
2、Class类
在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。
在执行程序(java.exe)的时候,将字节码文件读入JVM中,这个过程叫类的加载。然后在内存中对应创建一个java.lang.Class对象,这个对象会被放在字节码信息中,这个Class对象就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。
(1)获取字节码信息的方式
获取字节码信息的方式共有四种:通过getClass()方法获取、通过内置class属性获取、调用Class类提供的静态方法forName(最常用)、利用类的加载器(了解)。
自定义一个Person类,以获取该类对应的字节码信息为例:
//获取Person的字节码信息
//方式1:通过getClass()方法获取
Person p = new Person();
Class c1 = p.getClass();
System.out.println(c1);
//方式2:通过内置class属性获取
Class c2 = Person.class;
System.out.println(c2);
//方式3:调用Class类提供的静态方法forName(最常用)
Class c3 = Class.forName("myJavaTest.ReflectTest.Person");
System.out.println(c3);
//方式4:利用类的加载器(了解)
ClassLoader classLoader = Test.class.getClassLoader();
Class c4 = classLoader.loadClass("myJavaTest.ReflectTest.Person");
(2)Class类的实例的种类
Class类的具体实例可以为:
(1)类:外部类、内部类
(2)接口
(3)注解
(4)数组
(5)基本数据类型
(6)void
代码示例:
Class c1 = Person.class;
Class c2 = Comparable.class;
Class c3 = Override.class;
int[] arr1 = {1, 2, 3};
int[] arr2 = {4, 5, 6};
Class c4 = arr1.getClass();
Class c5 = arr2.getClass();
System.out.println(c4 == c5);//结果为true,同一个纬度,同一个元素类型,得到的字节码就是同一个
Class c6 = int.class;
Class c7 = void.class;
3、获取相关信息(示例)
利用上述方法得到字节码信息后,便可以利用该字节码信息获取相应信息。包括:属性、方法、构造器、接口、所在包、注解等。所有方式均大同小异,掌握一种其余都能快速上手。
(1)获取构造器和创建对象
方法 | 用途 |
---|---|
getConstructors() | 获取当前运行类的所有public构造方法 |
getDeclaredConstructors() | 获取当前运行类的所有构造方法 |
getConstructor(class…) | 获取当前运行类的指定构造方法(仅public) |
getDeclaredConstructor(class…) | 获取当前运行类的指定构造方法(所有) |
//获取Student类字节码信息
Class cls = Student.class;
//通过字节码信息可以获取构造器
//获取当前运行类的所有public构造方法
Constructor[] constructors = cls.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}
//获取当前运行类的所有构造方法
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
for(Constructor c : declaredConstructors){
System.out.println(c);
}
//获取当前运行类的指定构造方法
//获取空构造方法
Constructor constructor1 = cls.getConstructor();
System.out.println(constructor1);
//获取两个参数的有参构造方法
Constructor constructor2 = cls.getConstructor(String.class, double.class);
System.out.println(constructor2);
//获取非public的有参构造方法
Constructor declaredConstructor = cls.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);
//创建对象
//空构造器
Object o1 = constructor1.newInstance();
System.out.println(o1);
//有参构造器
Object o2 = constructor2.newInstance("name", 170.7);
System.out.println(o2);
(2)获取属性和对属性进行赋值
方法 | 用途 |
---|---|
getFields() | 获取当前运行类和父类所有被public修饰的属性 |
getDeclaredFields() | 获取当前运行类的所有属性 |
getField(“…”) | 获取当前运行类指定属性(仅public) |
getDeclaredField(“…”) | 获取当前运行类指定属性(所有) |
Field[] fields = cls.getFields();
for(Field f : fields){
System.out.println(f);
}
Field[] declaredFields = cls.getDeclaredFields();
Field field = cls.getField("sex");
Field declaredField = cls.getDeclaredField("height");
//获取属性的具体结构:修饰符、数据类型、名字等
//名字
height.getName();
//类型
height.getType().getName();
//修饰符
int modifiers = height.getModifiers();
System.out.println(Modifier.toString(modifiers));
//给属性赋值
Field sno = cls.getField("sno");
Object obj = cls.newInstance();
sno.set(obj, "male"); //给obj这个对象的sex属性设置具体的值
(3)获取方法和调用方法
方法 | 用途 |
---|---|
getMethods() | 获取当前运行类和所有父类中public方法 |
getDeclaredMethods() | 获取当前运行类的所有方法 |
getMethod() | 获取当前运行类的指定方法(仅public) |
getDeclaredMethod() | 获取当前运行类的指定方法(所有) |
susu//获取方法
Method[] methods = cls.getMethods();
Method[] declaredMethods = cls.getDeclaredMethods();
Method method = cls.getMethod("showInfo", int.class);
Method work = cls.getDeclaredMethod("work");
//获取方法的具体结构:修饰符、返回类型、方法名、参数列表、异常、注解等
//方法名
work.getName();
//修饰符
Modifier.toString(work.getModifiers());
//返回类型
work.getReturnType();
//参数列表
Class[] parameterTypes = work.getParameterTypes();
//注解
Annotation[] annotations = cls.getAnnotations();
//异常
Class[] exceptionTypes = work.getExceptionTypes();
//调用方法
Object o = cls.newInstance();
work.invoke(o);//调用o对象的work的无参方法
(4)获取类的接口,所在包,注解
方法 | 用途 |
---|---|
getInterfaces() | 获取类的接口 |
getAnnotations() | 获取类的注解 |
getPackage() | 获取类的包 |
//获取类的接口、注解、所在包
//接口
Class[] interfaces = cls.getInterfaces();
//得到父类的接口
Class[] interfaces1 = cls.getSuperclass().getInterfaces(); //先得到父类的字节码信息,再获取对应的接口
//注解
Annotation[] annotations = cls.getAnnotations();
//所在包
Package aPackage = cls.getPackage();
System.out.println(aPackage.getName()); //得到包名
4、补充
关于反射的两个问题:
(1)创建Person对象,用new Person()创建还是用反射创建?
或者说,何时用反射?
【摘自网络】new的对象的对应类必须在类路径下;反射中class.forName(“类路径”) 的参数指定的类文件可以不在类路径下,更加灵活,通过类加载器加载进内存后,调用无参构造方法实例化对象;在工厂模式中多使用反射机制,在增加类时,只需修改xml文件,通过反射便可创建对象,不需要修改框架源代码。
【摘自他文】我们如果用new,那么我们要创建的类都是已经“写死”在.class文件里面了,我们无法控制JVM帮我们加载类的这一项工作。如果我们用反射创建类对象,我们是相当于亲自“指导”JVM,我们“按需加载”.class文件,如果内存里面没有这个类的.class文件,那么我们用Class.forName()去叫类加载器帮忙加载就行了,而不是把程序停下来,再打一段代码,再让类加载器进行加载,从而体现出了Java的“动态性”。
原文链接:https://blog.csdn.net/qq_26558047/article/details/109745018
(2)反射是否破坏了面向对象的封装性?
【摘自他文】反射确实可以得到一切类中的东西(包括私有的属性、方法等),但是或许不算是破坏封装,私有方法是为了让继承的类无法使用,避免调用那些被设为私有的方法出现一些不必要的错误。这就是封装性。反射虽然可以获取私有方法并使用方法,只能说是其功能强大,可以在保证在调用私有方法不会出现错误,但是并没有反射调用方法后,该方法就不是私有的了。他仍然是私有的,仍然在子类中不可见。
原文连接:https://blog.csdn.net/CSDN___LYY/article/details/79518430