终于有大神把Java反射详解的明明白白了,网友:小白还不快收藏

568 阅读7分钟

Hello,今天给各位童鞋们分享的是Java反射,赶紧拿出小本子记下来吧

image.png

反射概述

什么是反射

将类的各个组成部分封装为其他对象的过程就叫做 反射,其中 组成部分 指的是我们类的 成员变量(Field)、构造方法(Constructor)、成员方法(Method)。

使用反射的优缺点

优点

  • 在程序运行过程中可以操作类对象,增加了程序的灵活性;
  • 解耦,从而提高程序的可扩展性,提高代码的复用率,方便外部调用;
  • 对于任何一个类,当知道它的类名后,就能够知道这个类的所有属性和方法;而对于任何一个对象,都能够调用它的一个任意方法。

缺点

  • 性能问题:Java 反射中包含了一些动态类型,JVM 无法对这些动态代码进行优化,因此通过反射来操作的方式要比正常操作效率更低。
  • 安全问题:使用反射时要求程序必须在一个没有安全限制的环境中运行,如果程序有安全限制,就不能使用反射。
  • 程序健壮性:反射允许代码执行一些平常不被允许的操作,破坏了程序结构的抽象性,导致平台发生变化时抽象的逻辑结构无法被识别。

Class 对象的获取及使用

获取 Class 对象的方式

Class.forName("全类名")

源代码阶段,它能将字节码文件加载进内存中,然后返回 Class 对象,多用于 配置文件 中,将类名定义在配置文件中,通过读取配置文件来加载类。

2.类名.class

类对象阶段,通过类名的 class 属性来获取,多用于 参数的传递。

3.对象.getClass()

运行时阶段,getClass() 定义在 Object 类中,表明所有类都能使用该方法,多用于 对象的获取字节码 的方式。

我们首先定义一个 Person 类,用于后续反射功能的测试;

image.png

image.png

image.png 定义好 Person 类之后,我们尝试用 3 种不同的方式来获取 Class 对象,并比较它们是否相同。

image.png

image.png 上述代码中,会发现最后输出的比较结果返回的是两个 true,说明通过上述三种方式获取的 Class 对象都是同一个,同一个字节码文件(*.class)在一次运行过程中只会被加载一次。

Class 对象的使用

获取成员变量

Field[] getFields()

image.png

image.png

回顾下我们的 Person 类,可以发现 id、grade 成员变量都是被 public 所修饰的,说明该方法是用于获取类中所有被 public 所修饰的成员变量(包括父类)。

Field getField(String name)

image.png

image.png

image.png 从上面的结果分析可知,该方法只能用于获取类中指定名称的 public 所修饰的成员变量,对于 protected、private 所修饰的成员变量,该方法是无法获取的(包括父类)。而获取或设置成员变量值时,可以通过 get/set 方法来操作,具体操作方法如下。

image.png Field[] getDeclaredFields()

image.png

image.png

观察上面的结果可知,该方法可用于获取所有的成员变量,不用考虑修饰符的限制(不包括父类)。

Field getDeclaredField(String name) image.png

image.png

观察上面的结果可知,该方法可用于获取指定的成员变量,不用考虑成员变量修饰符的限制(不包括父类)。但是在利用 set、get 方法来获取和设置 private、protected 修饰的成员变量时,需要利用 setAccessible() 来忽略访问全新啊修饰符的安全检查,否则程序将会报错。

获取构造方法

package com.cunyu;

import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;

/**

  • @author : cunyu
  • @version : 1.0
  • @className : Demo3
  • @date : 2021/4/8 13:28
  • @description : 构造对象获取 */

public class Demo3 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class personClass = Class.forName("com.cunyu.Person");

// 1. 获取所有构造方法 System.out.println("所有构造方法"); Constructor[] constructors = personClass.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); }

// 2. 获取指定构造方法

// 空参构造方法 System.out.println("空参构造方法"); Constructor constructor1 = personClass.getConstructor(); System.out.println(constructor1); // 带参构造方法 System.out.println("带参构造方法"); Constructor constructor2 = personClass.getConstructor(int.class, String.class, long.class, long.class, float.class, int.class); System.out.println(constructor2);

// 获取构造方法后,可以利用它来创建对象 System.out.println("空参创建对象"); // 第一种方法 Object person = constructor1.newInstance(); System.out.println(person); // 第二种方法 Object person1 = personClass.newInstance(); System.out.println(person1);

    System.out.println("带参创建对象");
    Object object = constructor2.newInstance(20, "村雨遥", 1312020, 3, 99.0F, 2);
    System.out.println(object);
}

}

image.png

类似于通过 Class 实例来获取成员变量,该方法用于获取所有 public 所修饰的构造方法(包括父类);

Constructor getConstructor(类<?>... parameterTypes)

该方法用于获取某一指定参数类型后的 public 所修饰的构造方法(包括父类);

Constructor<?>[] getDeclaredConstructors()

该方法用于获取所有 public 所修饰的构造方法(不包括父类);

Constructor getDeclaredConstructor(类<?>... parameterTypes)

该方法用于获取某一指定参数类型后的 public 所修饰的构造方法(不包括父类);

而获取到构造方法之后,我们就可以利用 newInstance() 方法来创建类的实例。特殊的,如果我们的构造方法是无参的,此时则可以直接利用 Class.newInstance() 来构造实例。

获取成员方法

package com.cunyu;

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;

/**

  • @author : cunyu
  • @version : 1.0
  • @className : Demo4
  • @date : 2021/4/8 13:51
  • @description : 成员方法获取 */

public class Demo4 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class personClass = Class.forName("com.cunyu.Person");

// 获取所有 public 成员方法 System.out.println("获取所有成员方法"); Method[] methods = personClass.getMethods(); for (Method method : methods) { System.out.println(method); }

// 获取指定名称的方法 System.out.println("获取指定名称的方法"); Method getAgeMethod = personClass.getMethod("getAge"); System.out.println(getAgeMethod);

// 执行方法 Person person = new Person(20, "村雨遥", 1312020, 3, 99.0F, 2); int age = (int) getAgeMethod.invoke(person); System.out.println(age);

}

}

image.png Method[] getMethods()

用于获取当前类的所有 public 所修饰的成员方法(包括父类)。

Method getMethod(String name, 类<?>... parameterTypes)

用于获取当前类的某一个指定名称 public 所修饰的成员方法(包括父类)。

Method[] getDeclaredMethods()

用于获取当前类的所有 public 所修饰的成员方法(不包括父类)。

Method getDeclaredMethods(String name, 类<?>... parameterTypes)

用于获取当前类的某一个指定名称 public 所修饰的成员方法(不包括父类)。

而当我们获取到类的成员方法后,如果要执行某一个方法,可以使用 invoke() 方法来执行该方法。

获取类名

image.png

image.png

String getName()

从上述程序的结果可知,当我们获取到 Class 对象之后,如果不知道类的全名,就可以使用 getName() 来获取该类的全名。

反射实例

假设我们有如下需求:在不改变类的代码的前提下,我们能够创建任意类的对象,并执行其中的方法。

此时,我们可以通过 配置文件 + 反射 的方式来实现这一效果,而这也就是我们现在所用框架中的基础,当我们使用反射后,只需要通过修改配置文件中的内容就能够不用去改代码就实现对应的功能。

假设我们有两个类,一个 Student,一个 Teacher,两者的定义如下;

image.png

image.png 要实现我们的需求,通常需要如下步骤:

1.将要创建对象的全类名和要执行的方法都配置在配置文件中;

定义的配置文件 prop.properties ,其中主要内容包括 className 和 methodName 两个属性,分别代表类的全类名和要调用方法的名字。一个具体实例如下,分别代表名为 Student 的类和名为 study 的方法。

image.png 2.然后在主方法中加载读取配置文件;

image.png 3.利用反射技术将类加载到内存中;

image.png 4.接着利用 newInstance() 方法创建对象;

image.png 5.最后则是利用 invoke() 方法来执行方法;

image.png 将整个流程汇总起来就是:

package com.cunyu;

import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties;

/**

  • @author : cunyu
  • @version : 1.0
  • @className : ReflectTest
  • @date : 2021/4/8 15:27
  • @description : 测试 */

public class ReflectTest { public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException { // 创建配置文件对象 Properties properties = new Properties(); // 加载配置文件 ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream("prop.properties"); properties.load(inputStream);

// 获取配置文件中定义的数据 String className = properties.getProperty("className"); String methodName = properties.getProperty("methodName");

// 加载进内存 Class name = Class.forName(className);

// 创建实例 Object object = name.newInstance();

// 获取并执行方法 Method method = name.getMethod(methodName); method.invoke(object); } }

此时,我们只需要改动配置文件 prop.properties 中的配置即可输出不同结果;

image.png

image.png 好啦,今天的文章就到这里了,希望能够帮助到屏幕前迷茫的你们