Java中的反射机制讲解! 反射的使用指导说明

举报
攻城狮Chova 发表于 2022/05/16 11:51:31 2022/05/16
【摘要】 本篇文章详细介绍了反射的基本概念以及反射的原理和主要用途。介绍了反射的几个主要的使用场景,包括获得Class对象,判断类的实例,创建实例,获取方法,获取构造器信息,获取类的成员变量信息,创建数组等。深入介绍了invoke方法的执行过程和执行原理。

反射的概念

  • 反射: Refelection,反射是Java的特征之一,允许运行中的Java程序获取自身信息,并可以操作类或者对象的内部属性
    • 通过反射,可以在运行时获得程序或者程序中的每一个类型的成员活成成员的信息
    • 程序中的对象一般都是在编译时就确定下来,Java反射机制可以动态地创建对象并且调用相关属性,这些对象的类型在编译时是未知的
    • 也就是说 ,可以通过反射机制直接创建对象,即使这个对象类型在编译时是未知的
  • Java反射提供下列功能:
    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法,可以通过反射调用private方法
    • 在运行时调用任意一个对象的方法

反射的原理

  • 反射的核心: JVM在运行时才动态加载类或者调用方法以及访问属性,不需要事先(比如编译时)知道运行对象是什么
  • 类的加载:
    • Java反射机制是围绕Class类展开的
    • 首先要了解类的加载机制:
      • JVM使用ClassLoader将字节码文件,即 class文件加载到方法区内存中
Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");

ClassLoader类根据类的完全限定名加载类并返回一个Class对象

  • ReflectionData:
    • 为了提高反射的性能,必须要提供缓存
    • class类内部使用一个useCaches静态变量来标记是否使用缓存
    • 这个值可以通过外部的sun.reflect.noCaches配置是否禁用缓存
    • class类内部提供了一个ReflectionData内部类用来存放反射数据的缓存,并声明了一个reflectionData
    • 由于稍后进行按需延迟加载并缓存,所以这个域并没有指向一个实例化的ReflectionData对象
// 标记是否使用缓存,可以通过外部的sun.reflect.noCaches配置是否禁用缓存
private static boolean useCaches = true;

static class ReflectionData<T> {
	volatile Field[] declaredFields;
	volatile Field[] publicFields;
	volatile Method[] declaredMethods;
	volatile Method[] publicMethods;
	volatile Constructor<T>[] declaredConstructors;
	volatile Constructors<T>[] publicConstructors;
	volatile Field[] declaredPublicFields;
	volatile Method[] declaredPublicMethods;
	final int redefinedCount;

	ReflectionData(int redefinedCount) {
		this.redefinedCount = redefinedCount;
	}
}
	
	// 这个是SoftReference,在内存资源紧张的时候可能会被回收
	// volatile保证多线程环境下读写的正确性
	 private volatile transient SoftReference<RefelectionData<T>> reflectionData;

	// 这个主要用于和ReflectionData中的redefinedCount进行比较
	// 如果两个值不相等,说明ReflectionData缓存的数据已经过期了
	private volatile transient classRedefinedCount = 0;

反射的主要用途

  • 反射最重要的用途就是开发各种通用框架
    • 很多框架都是配置化的,通过XML文件配置Bean
    • 为了保证框架的通用性,需要根据配置文件加载不同的对象或者类,调用不同的方法
    • 要运用反射,运行时动态加载需要加载的对象
  • 示例:
    • 在运用Struts 2框架的开发中会在struts.xml中配置Action:
	  <action name="login"
               class="org.ScZyhSoft.test.action.SimpleLoginAction"
               method="execute">
           <result>/shop/shop-index.jsp</result>
           <result name="error">login.jsp</result>
       </action>
  • 配置文件与Action建立映射关系
  • View层发出请求时,请求会被StrutsPrepareAndExecuteFilter拦截
  • StrutsPrepareAndExecuteFilter会动态地创建Action实例
    • 请求login.action
    • StrutsPrepareAndExecuteFilter会解析struts.xml文件
    • 检索actionnameloginAction
    • 根据class属性创建SimpleLoginAction实例
    • 使用invoke方法调用execute方法
  • 反射是各种容器实现的核心

反射的运用

  • 反射相关的类在StrutsPrepareAndExecuteFilter
  • 反射可以用于:
    • 判断对象所属的类
    • 获得class对象
    • 构造任意一个对象
    • 调用一个对象
  • 九大预定义的Class对象:
    • 基本的Java类型: boolean, byte, char, short, int, long, float, double
    • 关键字void通过class属性的也表示为Class对象
    • 包装类或者void类的静态TYPE字段:
      • Integer.TYPE == int.class
      • Integer.class == int.class
    • 数组类型的Class实例对象:
      • Class<String[]> clz = String[].class;
      • 数组的Class对象如何比较是否相等:
        • 数组的维数
        • 数组的类型
        • Class类中的isArray(),用来判断是否表示一个数组类型

获得Class对象

  • 使用Class类的forName静态方法:
public static Class<?> forName(String className);



/* 在JDBC中使用这个方法加载数据库驱动 */
Class.forName(driver);
  • 直接获取一个对象的class:
Class<?> klass=int.class;
Class<?> classInt=Integer.TYPE;
  • 调用对象的getClass()方法:
StringBuilder str=new StringBuilder("A");
Class<?> klass=str.getClass();

判断是否是某个类的实例

  • 一般来说,使用instanceof关键字判断是否为某个类的实例
  • 在反射中,可以使用Class对象的isInstance() 方法来判断是否为某个类的实例,这是一个native方法
public native boolean isInstance(Object obj);

创建实例

通过反射生成对象的实例主要有两种方式:

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例:
Class<?> c = String.class;
Object str = c.newInstance();
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例: 可以用指定的构造器构造类的实例
/* 获取String所对应的Class对象 */
Class<?> c=String.class;

/* 获取String类带一个String参数的构造器 */
Constructor constructor=c.getConstructor(String.class);

/* 根据构造器创建实例 */
Object obj=constructor.newInstance("abc");
System.out.println(obj);

获取方法

获取Class对象的方法集合,主要有三种方法:

  • getDeclaredMethods(): 返回类或接口声明的所有方法:
    • 包括公共,保护,默认(包)访问和私有方法
    • 不包括继承的方法
public Method[] getDeclaredMethods() throws SecurityException {}
  • getMethods(): 返回某个类所有的public方法
    • 包括继承类的public方法
public Method[] getMethods() throws SecurityException {}
  • getMethod(): 返回一个特定的方法
    • 第一个参数 :方法名称
    • 后面的参数 :方法的参数对应Class的对象
public Method getMethod(String name,Class<?>... parameterType) {}
  • 获取方法示例:
public class MethodTest {
	public static void methodTest() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Class<?> c = methodClass.class;
		Object object = c.newInstance();
		Method[] methods = c.getMethods();
		Method[] declaredMethods = c.getDeclaredMethods();
		// 获取methodClass类中的add方法
		Method method = c.getMethod("add", int.class, int.class);
		// getMethods()方法获取的所有方法
		System.out.println("getMethods获取的方法:");
		for (Method m:methods)
			System.out.println(m);
		// getDeclaredMethods()方法获取的所有方法
		System.out.println("getDeclaredMethods获取的方法:");
		for (Method m:declaredMethods)
			System.out.println(m);
	}
}

class methodClass {
	public final int n = 3;
	
	public int add(int a, int b) {
		return a + b;
	}
	
	public int sub(int a, int b) {
		return a * b;
	}
}

程序运行结果:

getMethods获取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
getDeclaredMethods获取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)

通过getMethods() 获取的方法可以获取到父类的方法

获取构造器信息

  • 通过Class类的getConstructor方法得到Constructor类的一个实例
  • Constructor类中newInstance方法可以创建一个对象的实例:
public T newInstance(Objec ... initargs)

newInstance方法可以根据传入的参数来调用对应的Constructor创建对象的实例

获取类的成员变量信息

  • getFileds: 获取公有的成员变量
  • getDeclaredFields: 获取所有已声明的成员变量,但是不能得到父类的成员变量

调用方法

  • 从类中获取一个方法后,可以使用invoke() 来调用这个方法
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {}
  • 示例:
public class InvokeTest {
	public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Class<?> klass = method.class;
		// 创建methodClass的实例
		Object obj = klass.newInstance();
		// 获取methodClass的add方法
		Method method = klass.getMethod("add", int.class, int.class);
		// 调用method对应的方法,实现add(1,4)
		Object result = method.invoke(obj, 1, 4);
		System.out.println(result);
	}
}

class methodClass {
	public final int n = 3;
	public int add(int a, int b) {
		return a + b;
	}
	public int sub(int a,int b) {
		return a * b;
	}
}

利用反射创建数组

  • 数组是Java中一种特殊的数据类型,可以赋值给一个Object Reference
  • 利用反射创建数组的示例:
public static void ArrayTest() throws ClassNotFoundException {
	Class<?> cls = class.forName("java.lang.String");
	Object array = Array.newInstance(cls, 25);
	// 在数组中添加数据
	Array.set(array, 0, "C");
	Array.set(array, 1, "Java");
	Array.set(array, 2, "Python");
	Array.set(array, 3, "Scala");
	Array.set(array, 4, "Docker");
	// 获取数据中的某一项内容
	System.out.println(Array.get(array, 3));
}

Array类是java.lang.reflect.Array类,通过Array.newInstance() 创建数组对象:

public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
	return newArray(componentType, length);
}

newArray方法是一个native方法,具体实现在HotSpot JVM中,源码如下:

private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


- newArray源码目录: openjdk\hotspot\src\share\vm\runtime\reflection.cpp

arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {
  if (element_mirror == NULL) {
    THROW_0(vmSymbols::java_lang_NullPointerException());
  }
  if (length < 0) {
    THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
  }
  if (java_lang_Class::is_primitive(element_mirror)) {
    Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);
    return TypeArrayKlass::cast(tak)->allocate(length, THREAD);
  } else {
    Klass* k = java_lang_Class::as_Klass(element_mirror);
    if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {
      THROW_0(vmSymbols::java_lang_IllegalArgumentException());
    }
    return oopFactory::new_objArray(k, length, THREAD);
  }
}
  • Array类的setget方法都是native方法,具体实现在HotSpot JVM中,对应关系如下:
    • set: Reflection::array_set
    • get: Reflection::array_get

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。