深入浅出反射

Posted nullZgy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入浅出反射相关的知识,希望对你有一定的参考价值。

技术分享


什么是反射?

反射是一种能够在程序运行时动态访问、修改某个类中任意属性(状态)和方法(行为)的机制(包括private实例和方法),java反射机制提供了以下几个功能:

  • 在运行时判断任意一个对象所属的类;

  • 在运行时构造任意一个类的对象;

  • 在运行时判断任意一个类所具有的成员变量和方法;

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

反射涉及到四个核心类:

  • java.lang.Class.java:类对象;

  • java.lang.reflect.Constructor.java:类的构造器对象;

  • java.lang.reflect.Method.java:类的方法对象;

  • java.lang.reflect.Field.java:类的属性对象;


反射有什么用?

  • 操作因访问权限限制的属性和方法;

  • 实现自定义注解;

  • 动态加载第三方jar包,解决android开发中方法数不能超过65536个的问题;

  • 按需加载类,节省编译和初始化APK的时间;



反射工作原理

当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。

反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。


反射实例

分别演示三种获取类信息的方式、获取当前类的所有方法和获取当前类及其父类的所有方法、获取当前类的所有实例和获取当前类及其父类的所有实例、获取父类信息、获取接口信息、比较反射方法和实例的性能差异等几个方面:



  • 示例类:

父类Personon.java:

<pre name="code" class="java">package com.eebbk.reflectdemo;

public class Person {
	String mName;
	String mSex;
	public int mAge;

	public Person(String aName, String aSex, int aAge) {
		mName = aName;
		mSex = aSex;
		mAge = aAge;
	}

	public int getmAge() {
		return mAge;
	}

	public void setmAge(int mAge) {
		this.mAge = mAge;
	}

	public String getmName() {
		return mName;
	}

	public void setmName(String mName) {
		this.mName = mName;
	}

	public String getmSex() {
		return mSex;
	}

	public void setmSex(String mSex) {
		this.mSex = mSex;
	}

	private String getDescription() {
		return "黄种人";
	}
}




接口ICompany.java:


package com.eebbk.reflectdemo;

public interface ICompany{
    String getCompany();
}

子类ProgramMonkey.java:

package com.eebbk.reflectdemo;

public class ProgramMonkey extends Person implements ICompany {
	String mLanguage = "C#";
	String mCompany = "BBK";

	public ProgramMonkey(String aName, String aSex, int aAge) {
		super(aName, aSex, aAge);
	}

	public ProgramMonkey(String language, String company, String aName,
			String aSex, int aAge) {
		super(aName, aSex, aAge);
		mLanguage = language;
		mCompany = company;
	}

	public String getmLanguage() {
		return mLanguage;
	}

	public void setmLanguage(String mLanguage) {
		this.mLanguage = mLanguage;
	}

	private int getSalaryPerMonth() {
		return 12306;
	}

	@Override
	public String getCompany() {
		return mCompany;
	}
}

示例类ReflectActivity.java:


public class ReflectActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_reflect_layout);
	}

	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.getClassObjectBtnId: {
			getClassObject();
		}
			break;
		case R.id.getMethodInfoBtnId: {
			getMethodInfo();
		}
			break;
		case R.id.getFieldInfoBtnId: {
			getFieldInfo();
		}
			break;
		case R.id.getSuperClassInfoBtnId: {
			getSuperClass();
		}
			break;
		case R.id.getInterfaceInfoBtnId: {
			getInterfaces();
		}
			break;
		case R.id.compareMethodAndFieldBtnId: {
			compareCallMethodAndField();
		}
			break;
		default: {
		}
			break;
		}
	}

	private void getClassObject() {
		Class<?> classObject = null;
		classObject = getClassObject_1();
		LogE("classObject_1 name : " + classObject.getName());
		classObject = getClassObject_2();
		LogE("classObject_2 name : " + classObject.getName());
		classObject = getClassObject_3();
		LogE("classObject_3 name : " + classObject.getName());
	}

	private void getMethodInfo() {
		getAllMethods();
		getCurrentClassMethods();
	}

	private void getFieldInfo() {
		getAllFields();
		getCurrentClassFields();
	}

	private void getSuperClass() {
		ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
		Class<?> superClass = programMonkey.getClass().getSuperclass();
		while (superClass != null) {
			Log.e("programMonkey's super class is : " + superClass.getName());
			// 再获取父类的上一层父类,直到最后的 Object 类,Object 的父类为 null
			superClass = superClass.getSuperclass();
		}
	}

	private void getInterfaces() {
		ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
		Class<?>[] interfaceses = programMonkey.getClass().getInterfaces();
		for (Class<?> class1 : interfaceses) {
			Log.e("programMonkey's interface is : " + class1.getName());
		}
	}

	private void compareCallMethodAndField() {
		long callMethodCostTime = getCallMethodCostTime(10000);
		Log.e("callMethodCostTime == " + callMethodCostTime);
		long callFieldCostTime = getCallFieldCostTime(10000);
		Log.e("callFieldCostTime == " + callFieldCostTime);
	}

	private long getCallMethodCostTime(int count) {
		long startTime = System.currentTimeMillis();
		for (int index = 0; index < count; index++) {
			ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
			try {
				Method setmLanguageMethod = programMonkey.getClass().getMethod(
						"setmLanguage", String.class);
				setmLanguageMethod.setAccessible(true);
				setmLanguageMethod.invoke(programMonkey, "Java");
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			}
		}
		return System.currentTimeMillis() - startTime;
	}

	private long getCallFieldCostTime(int count) {
		long startTime = System.currentTimeMillis();
		for (int index = 0; index < count; index++) {
			ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
			try {
				Field ageField = programMonkey.getClass().getDeclaredField(
						"mLanguage");
				ageField.set(programMonkey, "Java");
			} catch (NoSuchFieldException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return System.currentTimeMillis() - startTime;
	}
	

	/** * 获取当前类中的所有方法 * * */
	private void getCurrentClassMethods() {
		ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
		Method[] methods = programMonkey.getClass().getDeclaredMethods();
		for (Method method : methods) {
			LogE("declared method name : " + method.getName());
		}
		try {
			Method getSalaryPerMonthMethod = programMonkey.getClass().getDeclaredMethod("getSalaryPerMonth");
			getSalaryPerMonthMethod.setAccessible(true);
			// 获取返回类型
			Class<?> returnType = getSalaryPerMonthMethod.getReturnType();
			Log.e("getSalaryPerMonth 方法的返回类型 : " + returnType.getName());
			// 获取方法的参数类型列表
			Class<?>[] paramClasses = getSalaryPerMonthMethod.getParameterTypes();
			for (Class<?> class1 : paramClasses) {
				Log.e("getSalaryPerMonth 方法的参数类型 : " + class1.getName());
			}
			// 是否是 private 函数,属性是否是 private 也可以使用这种方式判断
			LogE(getSalaryPerMonthMethod.getName()+ " is private "+ Modifier.isPrivate(getSalaryPerMonthMethod.getModifiers()));
			// 执行方法
			Object result = getSalaryPerMonthMethod.invoke(programMonkey);
			LogE("getSalaryPerMonth 方法的返回结果: " + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	 /** * 获取当前类和父类的所有公有方法 * * */ 
	private void getAllMethods() {
		ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
		// 获取当前类和父类的所有公有方法
		Method[] methods = programMonkey.getClass().getMethods();
		for (Method method : methods) {
			Log.e("method name : " + method.getName());
		}
		try {
			Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
			setmLanguageMethod.setAccessible(true);
			// 获取返回类型
			Class<?> returnType = setmLanguageMethod.getReturnType();
			Log.e("setmLanguage 方法的返回类型 : " + returnType.getName());
			// 获取方法的参数类型列表
			Class<?>[] paramClasses = setmLanguageMethod.getParameterTypes();
			for (Class<?> class1 : paramClasses) {
				Log.e("setmLanguage 方法的参数类型 : " + class1.getName());
			}
			// 是否是 private 函数,属性是否是 private 也可以使用这种方式判断
			Log.e(setmLanguageMethod.getName() + " is private "+ Modifier.isPrivate(setmLanguageMethod.getModifiers()));
			// 执行方法
			Object result = setmLanguageMethod.invoke(programMonkey, "Java");
			Log.e("setmLanguage 方法的返回结果: " + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private Class<?> getClassObject_1() {
		return ProgramMonkey.class;
	}

	private Class<?> getClassObject_2() {
		ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
		return programMonkey.getClass();
	}

	private Class<?> getClassObject_3() {
		try {
			return Class.forName("com.eebbk.reflectdemo.ProgramMonkey");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}
	

	 /** * 得到当前类的所有实例 * * */ 
	private void getCurrentClassFields() {
		ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
		// 获取当前类的所有属性
		Field[] publicFields = programMonkey.getClass().getDeclaredFields();
		for (Field field : publicFields) { 
			Log.e("declared field name : " + field.getName()); 
			} try { 
				// 获取当前类的某个属性
				Field ageField = programMonkey.getClass().getDeclaredField("mAge"); 
				// 获取属性值
				Log.e(" my age is : " + ageField.getInt(programMonkey));
				// 设置属性值
				ageField.set(programMonkey, 10);
				LogE(" my age is : " + ageField.getInt(programMonkey)); 
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

 /** * 得到当前类和父类的所有公有属性 * * */ 
private void getAllFields() {
	ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
	// 得到当前类和父类的所有公有属性
	Field[] publicFields = programMonkey.getClass().getFields();
          for (Field field : publicFields) {
<pre name="code" class="java">             try {
 Log.e("field name : " + field.getName()); // 获取当前类和父类的某个公有属性Field ageField = programMonkey.getClass().getField("mAge"); LogE(" age is : " + ageField.getInt(programMonkey)); ageField.set(programMonkey, 8); LogE(" my age is : " + ageField.getInt(programMonkey));
} catch (Exception e) { e.printStackTrace(); } } }





  • 演示结果:

三种获取类信息的方式:

技术分享


获取当前类的方法、获取当前类和父类的所有公有方法:


技术分享


获取当前类的所有实例、获取当前类和父类的所有公有实例:



技术分享


获取父类信息:

技术分享


获取接口信息:

技术分享


比较反射方法和实例的性能差异:


技术分享


通过上面的示例可以发现,通过反射能够完成之前所描述的事情,并且反射方法比反射实例要慢很多。


反射的特点

优点

  • 灵活、自由度高:不受类的访问权限限制,想对类做啥就做啥;

缺点

  • 性能问题:通过反射访问、修改类的属性和方法时会远慢于直接操作,但性能问题的严重程度取决于在程序中是如何使用反射的。如果使用得很少,不是很频繁,性能将不会是什么问题;

  • 安全性问题:反射可以随意访问和修改类的所有状态和行为,破坏了类的封装性,如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;

  • 兼容性问题:因为反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常;

说明

  • 通过反射访问方法比实例慢很多;

  • 有用到反射的类不能被混淆;

  • 反射存在性能问题,但使用不频繁、按需使用时,对程序性能影响并不大;

  • 反射存在安全性问题,因为可以随意修改类的所有状态和行为(包括private方法和实例);

  • 使用反射访问Android的API时需要注意因为不同API版本导致的兼容性问题;

















































以上是关于深入浅出反射的主要内容,如果未能解决你的问题,请参考以下文章

C++反射:深入浅出剖析ponder库实现机制!

深入理解 Java 中的反射机制

深入浅出Java!java反射复制对象

为啥我的 Ray March 片段着色器反射纹理查找会减慢我的帧速率?

OpenGL片段着色器不照亮场景

《深入理解Java虚拟机》- JVM是如何实现反射的