SE高阶(11):Java反射机制—动态获取类的所有信息

什么是反射机制?

        Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为java的反射机制。


反射机制主要提供以下功能:

  • 在运行时判定任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判定任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。

反射的优点与缺点:

  1. 反射能在运行时获取类指定信息,可以增加程序灵活性。在框架中就大量通过反射来增加程序灵活性,避免程序写死在源代码中。
  2. 反射包括了一些动态类型,JVM 无法对这些代码进行优化,因此反射操作的效率要比那些非反射操作低得多。
  3. 如果一个功能能不用反射实现就不用反射,但有些地方为了保证灵活性也不得不用,所以酌情考虑。

简单举例:如果实例化一个类的对象,常规是使用new操作符,但这样就意味着创建的对象是固定的,要修改成别的就需要更改源代码,而反射则可以通过Class实例在运行时实例化对象。如果要修改实例化的对象只需要修改配置文件就行了,极大地增加了程序灵活性,避免修改代码然后重新编译。 


利用反射获取类的信息

使用反射的前提是要得到Class实例,每一个类都有对应的Class实例,类加载进内存时JVM会返回一个Class实例。

获取Class实例三种方式

  • 使用Class.forName():Class cl = Class.forName("com.reflect.Test02");
  • 根据对象:new Test01().getClass();
  • 根据类名:Class cla = Test01.class;
  • 使用Class.forName()方法需要加上包名,不然找不到该类。该方法会导致类初始化。

//用于测试反射的类
public class Test02 {
	private int a = 0;
	private String s = "私有字段";
	protected int c = 6;
	public String str = "公有字段";
	//构造器
	private Test02() {System.out.println("无参构造器私有化...");}
	public Test02(int a, String s, int c, String str) {
		this.a = a;
		this.s= s;
		this.c = c;
		this.str = str;
	}	
	//方法
	private void priMethod() {System.out.println("私有方法");}
	protected void proMethod() {System.out.println("protected权限方法");}
	void method(String def) {System.out.println("默认权限方法");}
	public void pubMethod() {System.out.println("public权限方法");}
}

获取字段变量(Field)

Class cla = Test02.class;
Field[] f = cla.getFields();//只能获取public权限字段变量
Field[] f2 = cla.getDeclaredFields();//获取所有权限的字段变量
for(Field result : f)
	System.out.println(result);
System.out.println("-------------------------");
for(Field result : f2)
	System.out.println(result);
//获取指定的字段变量
Field f3 = cla.getField("c"); //c字段变量非public,该方法无法获取
Field f4 = cla.getDeclaredField("c");
  • 使用说明:获取字段有两种方式,获取指定字段或者获取所有字段。没带Declared的只能获取public权限,有Declared的方法能获取到所有权限,获取构造器和方法的使用方式都是一致的。
//		Test02 tes = cla.newInstance();//error,因为Test02的默认构造器私有化,所以无法创建实例
		Test02 test = new Test02(5, "A", 8, "B");//创建对象
		Field df = cla.getDeclaredField("str");//str字段是public权限
		System.out.println(df.get(test));//结果输出B
获取字段变量后就能访问吗?先给例子来给予说明
		Test02 test = new Test02(5, "A", 8, "B");
		Field df = cla.getDeclaredField("a");//a字段是私有权限
		System.out.println(df.isAccessible());//false	
		System.out.println(df.get(test));//引发非法访问异常
上面的例子能看出获取访问权限都是false,但public权限字段能被访问,而访问其他权限字段会引发异常,这就需要使用setAccessible()方法。

启用/关闭访问权限检查setAccessible()

setAccessiable()方法属于AccessObject类,该类是Field、Method、Constructor的父类,该方法三者通用。传入true允许访问,false表示不允许访问。
		Class cla = Test02.class;
		Test02 test = new Test02(5, "A", 8, "B");
		Field df = cla.getDeclaredField("a");//a字段是私有权限
		System.out.println(df.isAccessible());//false
		//取消安全访问检查
		df.setAccessible(true);
		System.out.println(df.get(test));//可以访问该值	

Field的简单案例

		System.out.println("字段名:" + df.getName());
		System.out.println("字段权限修饰符:" + df.getModifiers());
		System.out.println("字段类型:" + df.getType());
		System.out.println("字段的泛型类型参数:" + df.getGenericType());
  • 使用说明:除了以上,还提供getXxx()和setXxx()方法可以很方便地处理字段类型为基本类型。使用get()/set()也行,Java提供了装箱拆箱机制。

=====================================================================================================================

获取构造器(Constructor)

		//获取Class实例,并初始化该类
		Class cla = Class.forName("com.reflect.Test02");
		//获得全部构造器,无视权限
		Constructor[] con = cla.getDeclaredConstructors();
		for(Constructor c : con)
			System.out.println(c);
		System.out.println("------------------------------");
		//无参数传入,返回默认无参构造器
		Constructor constr = cla.getDeclaredConstructor();
		//该类默认构造器私有化,需要取消访问检查
		constr.setAccessible(true);
		Object obj = constr.newInstance();//通过该无参构造器生成一个实例
		//传入Class参数,返回对应的有参构造器
		Constructor co = cla.getDeclaredConstructor(int.class, String.class, int.class, String.class);

  • 使用说明:因为默认构造器被私有化,无法通过Class实例的newInstance()来得到对象,Class实例的newInstance()方法本质上就是通过反射得到默认的Constructor,然后调用newInstance()。上述代码中通过反射获得私有化构造器,调用newInstance()就能生成该Class实例对应对象,但这样做严重破坏了程序封装性,可以直接访问private,所以需要注意。

Constructor的简单案例

Constructor主要用处就是生成对象,其自带的方法也和Field差不多,都是用于获取Constructor的信息。
		//传入Class参数,返回对应的构造器
		Constructor co = cla.getDeclaredConstructor(int.class, String.class, int.class, String.class);
		//获取所有参数类型,返回Class实例数组
		Class[] paramt = co.getParameterTypes();
		for(Class para : paramt)
			System.out.print(para + ", ");
		//参数数量
		System.out.println(co.getParameterCount());
		//获取该Constructor中所有参数
		Parameter[] par = co.getParameters();
		for(Parameter p: par)
			System.out.print(p + ", ");


=====================================================================================================================

获取方法(Method)

	public static void main(String[] args) throws Exception {
		//获取Class实例
		Class cla = Test02.class;
		Method[] me = cla.getDeclaredMethods();//返回cla中声明的所有方法
		for(Method m : me) {
			System.out.println(m);
			System.out.println("方法名:" + m.getName());
			System.out.println("修饰符:" + m.getModifiers());
			Class[] para = m.getParameterTypes();
			for(Class p : para) {
				System.out.println("方法的参数类型:" + p);
			}
			System.out.println("返回类型:" + m.getReturnType());
			System.out.println("方法参数数量:" + m.getParameterCount());
			System.out.println("=============================================");
		}
		//获得指定方法
		Method method = cla.getDeclaredMethod("method", String.class);
		Test02 test = new Test02(5, "A", 8, "B");//有参构造创建对象,因为默认构造器私有化
		//通过反射调用该方法
		method.invoke(test, "@#$%");
	}


反射的小知识点

  • 除以上之外,反射还可以用于获取类的注解信息,使用方式差不多。
  • 反射中的setAccessible()用于设置私有领域的访问检查,该方法会打破封装性,谨慎使用。
  • Class实例直接调用newInstance(),如果默认构造器私有化,会引发异常。


你可能感兴趣的:(Java基础笔记)