Java——反射机制

Love The Way You Lie 2021-06-10 20:41 466阅读 0赞

1.简介

为什么要用反射来获取对象:

  • new属于静态编译
  • 反射属于动态编译,只有到运行时他才会去获得该对象的实例

几种获取class对象的方法:

  • 1.通过ClassLoader对象的loadClass()方法:
    不会对类进行初始化。

    ClassLoader.getSystemClassLoader().loadClass(“com.my.test.Hello”)

  • 2.类名.class
    仅适合在编译前就已经明确要操作的 Class,同样不会对类进行初始化。

    Class clazz2 = User.class;

  • 3.Class.forName()
    前提:已明确类的全路径名。
    forName0是一个native方法。调用forName(string)方法等同于调用Class.forName(className, true, currentLoader)方法

    Class clazz1 = null;
    try {

    1. clazz1 = Class.forName("com.reflection.User");

    } catch (ClassNotFoundException e) {

    1. e.printStackTrace();

    }

  • 4.object.getClass()
    适合有对象示例的情况下

    User user = new User();
    Class clazz3 = user.getClass();

反射机制的定义:
反射就是把Java类中的各个成分映射成一个个的Java对象。即在运行状态中,对于任意一个类,都能够知道这个类的所以属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息及动态调用对象方法的功能叫Java的反射机制。

用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。

2. 为什么要用到反射?

优点

  • 反射可以在运行时加载、探知、使用完全未知的类,只需要知道类的路径,通过这个路径可以拿到我需要的东西,与类本身无关,灵活性更高了,类之间的耦合性也低。

缺点

  • (1)我们可以获取到类的私有信息,破坏了类的封装,使类变得不安全。使用反射还会降低程序性能。
  • (2)反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化.
  • (3)反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
  • 所以说使用反射优点突出,缺点同样突出。

反射应用到的场合:
(1)在运行时判断任意一个对象所属的类。

  1. Class aClass = Class.forName("com.Dan.Consumer");
  2. System.out.print("?"+aClass.isInstance(new Consumer()));
  3. // public boolean isInstance(Object obj)
  4. // 当该 Class 对象表示一个已声明的类时,若指定的 Object 参数是所表示类(或其任一子类)的一个实例,则此方法返回 true;否则返回 false。

(2)在运行时构造任意一个类的对象。

  1. Class aClass1 = Class.forName("com.Dan.Consumer");
  2. Object object = aClass1.newInstance();
  3. // throws InstantiationException, IllegalAccessException

(3)在运行时判断任意一个类所具有的成员变量和方法。

  1. Class aClass2 = Class.forName("com.Dan.Consumer");
  2. Method[] methods = aClass2.getDeclaredMethods();
  3. Field[] fields = aClass2.getDeclaredFields();

(4)在运行时调用任意一个对象的方法。

  1. Class aClass3 = Class.forName("com.Dan.Consumer");
  2. Method[] methods[]= aClass3.getDeclaredMethods();
  3. Object object = aClass3.newInstance();
  4. Object returnObject = methods.invoke(object,null);

简单的说就是程序运行时,如果需要外部传进来一个对象,然后在程序中运用这个对象,我们就可以通过配置文件中类的路径,得到这个对象的所有信息,从而加以应用。

Java的很多框架的底层都是用反射来实现的,比如Struts2,JDBC,SQLite

3. 反射的使用

Java里实现反射的那个类是java.lang.reflect,主要有以下四个类:

  • java.lang.reflect.AccessibleObject: 是另外三个类的基类
  • java.lang.reflect.Constructor:描述类的构造器,可以用 Constructor 创建新的对象。
  • java.lang.reflect.Field:描述类的域(属性),可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • java.lang.reflect.Method:描述类的方法,可以使用 invoke() 方法调用与 Method 对象关联的方法

介绍一下class类的常用方法:
在这里插入图片描述
一个实例:

3.1 准备两个类

定义两个类,Person和Employee,其中Employee继承自Person,且各自都有一个private,protected,public修饰的域(属性),Employee还有private,public修饰的方法

  1. public class Person {
  2. public String name; // 姓名 公有
  3. protected String age; // 年龄 保护
  4. private String hobby; // 爱好 私有
  5. public Person(String name, String age, String hobby) {
  6. this.name = name;
  7. this.age = age;
  8. this.hobby = hobby;
  9. }
  10. public String getHobby() {
  11. return hobby;
  12. }
  13. }
  14. public class Employee extends Person {
  15. public static Integer totalNum = 0; // 员工数
  16. public int empNo; // 员工编号 公有
  17. protected String position; // 职位 保护
  18. private int salary; // 工资 私有
  19. public void sayHello() {
  20. System.out.println(String.format("Hello, 我是 %s, 今年 %s 岁, 爱好是%s, 我目前的工作是%s, 月入%s元\n", name, age, getHobby(), position, salary));
  21. }
  22. private void work() {
  23. System.out.println(String.format("My name is %s, 工作中勿扰.", name));
  24. }
  25. public Employee(String name, String age, String hobby, int empNo, String position, int salary) {
  26. super(name, age, hobby);
  27. this.empNo = empNo;
  28. this.position = position;
  29. this.salary = salary;
  30. Employee.totalNum++;
  31. }
  32. }

3.2 反射获取class对象

反射获取 Class 对象的方法:

  • 使用 Class 类的 forName 静态方法;
  • 直接获取某一个对象的 class;
  • 调用某个对象的 getClass() 方法

    public class ClassTest {

    1. public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    2. Class c1 = Class.forName("reflect.Employee"); // 第1种,forName 方式获取Class对象
    3. Class c2 = Employee.class; // 第2种,直接通过类获取Class对象
    4. Employee employee = new Employee("小明", "18", "写代码", 1, "Java攻城狮", 100000);
    5. Class c3 = employee.getClass(); // 第3种,通过调用对象的getClass()方法获取Class对象
    6. if (c1 == c2 && c1 == c3) { // 可以通过 == 比较Class对象是否为同一个对象
    7. System.out.println("c1、c2、c3 为同一个对象");
    8. System.out.println(c1); // class reflect.Employee
    9. }
    10. }

    }

3.3 反射创建实例

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例

    public class NewInstanceTest {

    1. public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    2. Class c = Date.class;
    3. Date date1 = (Date) c.newInstance(); // 第1种方式:使用Class对象的newInstance()方法来创建Class对象对应类的实例
    4. System.out.println(date1); // Wed Dec 19 22:57:16 CST 2018
    5. long timestamp =date1.getTime();
    6. Constructor constructor = c.getConstructor(long.class);
    7. Date date2 = (Date)constructor.newInstance(timestamp); // 第2种方式:先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例
    8. System.out.println(date2); // Wed Dec 19 22:57:16 CST 2018
    9. }

    }

3.4 获取类的全部信息

上面我们定义了两个类,现在有个需求:获取Employee的类名,构造器签名,所有的方法,所有的域(属性)和值

获取类信息的部分API:
String getName(): 获取这个Class的类名

Constructor[] getDeclaredConstructors() :返回这个类的所有构造器的对象数组,包含保护和私有的构造器;相近的方法 getConstructors() 则返回这个类的所有公有构造器的对象数组,不包含保护和私有的构造器

Method[] getDeclaredMethods() :返回这个类或接口的所有方法,包括保护和私有的方法,不包括超类的方法;相近的方法 getMethods() :则返回这个类及其超类的公有方法的对象数组,不含保护和私有的方法

Field[] getDeclaredFields() :返回这个类的所有域的对象数组,包括保护域和私有域,不包括超类的域;还有一个相近的API getFields() :返回这个类及其超类的公有域的对象数组,不含保护域和私有域

int getModifiers() :返回一个用于描述Field、Method和Constructor的修饰符的整形数值,该数值代表的含义可通过Modifier这个类分析

Modifier 类 :它提供了有关Field、Method和Constructor等的访问修饰符的信息,主要的方法有:toString(int modifiers)返回整形数值modifiers代表的修饰符的字符串;isAbstract是否被abstract修饰;isVolatile是否被volatile修饰;isPrivate是否为private;isProtected是否为protected;isPublic是否为public;isStatic是否为static修饰;

实例:

  1. public class ReflectionTest {
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. String name;
  4. if (args.length > 0) {
  5. name = args[0];
  6. } else {
  7. Scanner in = new Scanner(System.in);
  8. System.out.println("输入一个类名(e.g. java.util.Date):"); // reflect.Employee
  9. name = in.next();
  10. }
  11. try {
  12. Class cl = Class.forName(name);
  13. Class superCl = cl.getSuperclass();
  14. String modifiers = Modifier.toString(cl.getModifiers());
  15. if (modifiers.length() > 0) {
  16. System.out.print(modifiers + " ");
  17. }
  18. System.out.print("class " + name);
  19. if (superCl != null && superCl != Object.class) {
  20. System.out.print(" extends " + superCl.getName());
  21. }
  22. System.out.println("\n{");
  23. printConstructors(cl); // 打印构造方法
  24. System.out.println();
  25. printMethods(cl); // 打印方法
  26. System.out.println();
  27. printFields(cl); // 打印属性
  28. System.out.println("}");
  29. } catch (ClassNotFoundException e) {
  30. e.printStackTrace();
  31. }
  32. System.exit(0);
  33. }
  34. /** * 打印Class对象的所有构造方法 */
  35. public static void printConstructors(Class cl) {
  36. Constructor[] constructors = cl.getDeclaredConstructors();
  37. for (Constructor c : constructors) {
  38. String name = c.getName();
  39. System.out.print(" ");
  40. String modifiers = Modifier.toString(c.getModifiers());
  41. if (modifiers.length() > 0) {
  42. System.out.print(modifiers + " ");
  43. }
  44. System.out.print(name + "(");
  45. // 打印构造参数
  46. Class[] paramTypes = c.getParameterTypes();
  47. for (int i = 0; i < paramTypes.length; i++) {
  48. if (i > 0) {
  49. System.out.print(", ");
  50. }
  51. System.out.print(paramTypes[i].getName());
  52. }
  53. System.out.println(");");
  54. }
  55. }
  56. /** * 打印Class的所有方法 */
  57. public static void printMethods(Class cl) {
  58. Method[] methods = cl.getDeclaredMethods();
  59. //Method[] methods = cl.getMethods();
  60. for (Method m : methods) {
  61. Class retType = m.getReturnType(); // 返回类型
  62. System.out.print(" ");
  63. String modifiers = Modifier.toString(m.getModifiers());
  64. if (modifiers.length() > 0) {
  65. System.out.print(modifiers + " ");
  66. }
  67. System.out.print(retType.getName() + " " + m.getName() + "(");
  68. Class[] paramTypes = m.getParameterTypes();
  69. for (int i = 0; i < paramTypes.length; i++) {
  70. if (i > 0) {
  71. System.out.print(", ");
  72. }
  73. System.out.print(paramTypes[i].getName());
  74. }
  75. System.out.println(");");
  76. }
  77. }
  78. /** * 打印Class的所有属性 */
  79. public static void printFields(Class cl) {
  80. Field[] fields = cl.getDeclaredFields();
  81. for (Field f: fields) {
  82. Class type = f.getType();
  83. System.out.print(" ");
  84. String modifiers = Modifier.toString(f.getModifiers());
  85. if (modifiers.length()> 0) {
  86. System.out.print(modifiers + " ");
  87. }
  88. System.out.println(type.getName() + " " + f.getName() + ";");
  89. }
  90. }
  91. }

运行程序,然后在控制台输入一个我们想分析的类的全名,譬如 reflect.Employee,可得到下面的输出

  1. 输入一个类名(e.g. java.util.Date):
  2. reflect.Employee
  3. public class reflect.Employee extends reflect.Person
  4. {
  5. private reflect.Employee(java.lang.String, java.lang.String, java.lang.String);
  6. public reflect.Employee(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, int);
  7. public static void main([Ljava.lang.String;);
  8. public void sayHello();
  9. private void work();
  10. public static java.lang.Integer totalNum;
  11. public int empNo;
  12. protected java.lang.String position;
  13. private int salary;
  14. }

3.5 运行时查看对象数据域的实际内容

在运行时查看对象的数据域的实际值。这个场景就像我们通过IDEA调试程序,设置断点拦截到程序后,查看某个对象的属性的值。

运行时查看对象数据域实际内容的相关API:

Class<?> getComponentType() 返回数组类里组件类型的 Class,如果不是数组类则返回null

boolean isArray() 返回这个类是否为数组,同类型的API还有 isAnnotation、isAsciiDigit、isEnum、isInstance、isInterface、isLocalClass、isPrimitive 等

int Array.getLength(obj) 返回数组对象obj的长度

Object Array.get(obj, i) 获取数组对象下标为i的元素

boolean isPrimitive() 返回这个类是否为8种基本类型之一,即是否为boolean, byte, char, short, int, long, float, 和double 等原始类型

Field getField(String name) 获取指定名称的域对象

AccessibleObject.setAccessible(fields, true) 当访问 Field、Method 和 Constructor 的时候Java会执行访问检查,如果访问者没有权限将抛出SecurityException,譬如访问者是无法访问private修饰的域的。通过设置 setAccessible(true) 可以取消Java的执行访问检查,这样访问者就获得了指定 Field、Method 或 Constructor 访问权限

Class<?> Field.getType() 返回一个Class 对象,它标识了此 Field 对象所表示字段的声明类型

Object Field.get(Object obj) 获取obj对象上当前域对象表示的属性的实际值,获取到的是一个Object对象,实际使用中还需要转换成实际的类型,或者可以通过 getByte()、getChar、getInt() 等直接获取具体类型的值

void Field.set(Object obj, Object value) 设置obj对象上当前域表示的属性的实际值

实例:

  1. public class ObjectAnalyzer {
  2. private ArrayList<Object> visited = new ArrayList<>();
  3. public String toString(Object obj) {
  4. if (obj == null) {
  5. return "null";
  6. }
  7. if (visited.contains(obj)) { // 如果该对象已经处理过,则不再处理
  8. return "...";
  9. }
  10. visited.add(obj);
  11. Class cl = obj.getClass(); // 获取Class对象
  12. if (cl == String.class) { // 如果是String类型则直接转为String
  13. return (String) obj;
  14. }
  15. if (cl.isArray()) { // 如果是数组
  16. String r = cl.getComponentType() + "[]{\n"; // 数组的元素的类型
  17. for (int i = 0; i < Array.getLength(obj); i++) {
  18. if (i > 0) { // 不是数组的第一个元素加逗号和换行,显示更加美观
  19. r += ",\n";
  20. }
  21. r += "\t";
  22. Object val = Array.get(obj, i);
  23. if (cl.getComponentType().isPrimitive()) { // Class为8种基本类型的时候为 true,直接输出
  24. r += val;
  25. } else {
  26. r += toString(val); // 不是8中基本类型时,说明是类,递归调用toString
  27. }
  28. }
  29. return r + "\n}";
  30. }
  31. // 既不是String,也不是数组时,输出该对象的类型和属性值
  32. String r = cl.getName();
  33. do {
  34. r += "[";
  35. Field[] fields = cl.getDeclaredFields(); // 获取该类自己定义的所有域,包括私有的,不包括父类的
  36. AccessibleObject.setAccessible(fields, true); // 访问私有的属性,需要打开这个设置,否则会报非法访问异常
  37. for (Field f : fields) {
  38. if (!Modifier.isStatic(f.getModifiers())) { // 通过 Modifier 可获取该域的修饰符,这里判断是否为 static
  39. if (!r.endsWith("[")) {
  40. r += ",";
  41. }
  42. r += f.getName() + "="; // 域名称
  43. try {
  44. Class t = f.getType(); // 域(属性)的类型
  45. Object val = f.get(obj); // 获取obj对象上该域的实际值
  46. if (t.isPrimitive()) { // 如果类型为8种基本类型,则直接输出
  47. r += val;
  48. } else {
  49. r += toString(val); // 不是8种基本类型,递归调用toString
  50. }
  51. } catch (IllegalAccessException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }
  56. r += "]";
  57. cl = cl.getSuperclass(); // 继续打印超类的类信息
  58. } while (cl != null);
  59. return r;
  60. }
  61. }

测试:

  1. public class ObjectAnalyzerTest {
  2. public static void main(String[] args) {
  3. int size = 4;
  4. ArrayList<Integer> squares = new ArrayList<>(size);
  5. for (int i = 0; i < size; i++) {
  6. squares.add(i * i);
  7. }
  8. ObjectAnalyzer objectAnalyzer = new ObjectAnalyzer(); // 创建一个上面定义的分析类ObjectAnalyzer的对象
  9. System.out.println(objectAnalyzer.toString(squares)); // 分析ArrayList<Integer>对象的实际值
  10. Employee employee = new Employee("小明", "18", "爱好写代码", 1, "Java攻城狮", 100); // 分析自定义类Employee的对象的实际值
  11. System.out.println(objectAnalyzer.toString(employee));
  12. }
  13. }

结果:

  1. java.util.ArrayList[elementData=class java.lang.Object[]{
  2. java.lang.Integer[value=0][][],
  3. java.lang.Integer[value=1][][],
  4. java.lang.Integer[value=4][][],
  5. java.lang.Integer[value=9][][]
  6. },size=4][modCount=4][][]
  7. reflect.Employee[empNo=1,position=Java攻城狮,salary=100][name=小明,age=18,hobby=爱好写代码][]

其中ArrayList打印了类名和5个元素的类型和值,Employee 打印了类名,自己定义的3个基本类型的属性的实际值,和父类Person的3个基本类型的属性的实际值

需要注意的是,position,age 是 protected 保护域,salary,hobby 是 private 私有域,Java的安全机制只允许查看任意对象有哪些域,但是不允许读取它们的值

通过 AccessibleObject.setAccessible(fields, true) 将域设置为了可访问,取消了Java的执行访问检查,因此可以访问,如果不加会报异常 IllegalAccessException

我们通过 setAccessible(true) 绕过了Java执行访问检查,因此能够访问私有域,通过 Field.getType() 获得了属性的声明类型,通过了 Field.get(Object obj) 获得了该域属性的实际值,还有一个没用上的 Field.set(Object obj, Object value) 设置域属性的实际值

3.6 调用任意方法

上面我们已经获取了类的构造器,方法,域,查看和设置了域的实际值,那么是不是还可以在调用对象的方法呢?

调用任意方法相关的API:

Method getMethod(String name, Class<?>… parameterTypes) 获取指定的 Method,参数 name 为要获取的方法名,parameterTypes 为指定方法的参数的 Class,由于可能存在多个同名的重载方法,所以只有提供正确的 parameterTypes 才能准确的获取到指定的 Method

Object invoke(Object obj, Object… args) 执行方法,第一个参数执行该方法的对象,如果是static修饰的类方法,则传null即可;后面是传给该方法执行的具体的参数值

实例:

  1. public class MethodTableTest {
  2. public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  3. Employee employee = new Employee("小明", "18", "写代码", 1, "Java攻城狮", 100000);
  4. Method sayHello = employee.getClass().getMethod("sayHello");
  5. System.out.println(sayHello); // 打印 sayHello 的方法信息
  6. sayHello.invoke(employee); // 让 employee 执行 sayHello 方法
  7. double x = 3.0;
  8. Method square = MethodTableTest.class.getMethod("square", double.class); // 获取 MethodTableTest 的square方法
  9. double y1 = (double) square.invoke(null, x); // 调用类方法 square 求平方,方法参数 x
  10. System.out.printf("square %-10.4f -> %10.4f%n", x, y1);
  11. Method sqrt = Math.class.getMethod("sqrt", double.class); // 获取 Math 的 sqrt 方法
  12. double y2 = (double) sqrt.invoke(null, x); // 调用类方法 sqrt 求根,方法参数 x
  13. System.out.printf("sqrt %-10.4f -> %10.4f%n", x, y2);
  14. }
  15. // static静态方法 计算乘方
  16. public static double square(double x) {
  17. return x * x;
  18. }
  19. }

结果:

  1. public void reflect.Employee.sayHello()
  2. Hello, 我是 小明, 今年 18 岁, 爱好是写代码, 我目前的工作是Java攻城狮, 月入100000
  3. square 3.0000 -> 9.0000
  4. sqrt 3.0000 -> 1.7321

发表评论

表情:
评论列表 (有 0 条评论,466人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java反射机制

    百科: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法

    相关 Java反射机制

    1、什么是反射技术? 动态获取指定类以及类中的内容(成员),并运行其内容。 应用程序已经运行,无法在其中进行new对象的建立,就无法使用对象。这时可以根据配置文件的类全名去

    相关 Java反射机制

    Java反射机制 > 定义:在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性(包括私有的方法和属性)进行调

    相关 java反射机制

    什么是反射 > 反射是一种计算机处理方式。有程序可以访问、检测和修改它本身状态或行为的这种能力。能提供封装程序集、类型的对象。(程序集包含模块,而模块包含类型