Reflection 反射

1,概念:所谓的反射,可以理解为在运行时获取对象类型信息的操作。

2,说明:

	1)Class对象是在加载类时由虚拟机以及通过调用类加载器中的defineClass方法自动构造的。
	2)一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,一个类在虚拟机中只有一份字节码


3,获得 Class 对象的三种方法:

	1)调用Objec类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。
		MyObject mobj = new MyObject(); 
		Class c1 = mobj.getClass();

	2)使用Class类的forName(String className)静态方法,className表示全限定名;如String的全限定名:java.lang.String;
		Class c2 = Class.forName("java.lang.String");

	3)调用某个类的class属性,即使用“类型名.class”来获取该类型对应的Class对象。注:基本类型和引用类型都有class属性
		引用类型:Class c1 = MyType.class;
		基本类型:Class c2 = int.class;
		数组:Class c3 = double[].class;

	注意:
		基本类型(boolean、byte、char、short、int、long、float和double)以及关键字void通过调用自己的class属性可以得到其Class对象
			说明:基本类型的包转类和Void类都有静态的TYPE字段,该字段返回的是他们基本类型的Class对象!
				即:Integer.TYPE == int.class;
			注意:int和Integer表示不同的数据类型,故int.class与Integer.class是不同的Class对象,所以Integer.TYPE与Integer.class也是不同的Class对象。

		数组:所有具有相同元素类型和维数的数组都共享同一个Class对象,和数组元素长度、元素的值以及元素的顺序无关

		
4,Class类中的方法:

	获得实现的接口:
		public Class<?>[] getInterfaces()
			确定此对象所表示的类或接口实现的接口

	获得构造方法:
		public Constructor<T> getConstructor(Class<?>... parameterTypes)
			获得Class所表示类中用public修饰的指定构造器,注:当没有传入参数时,即 getConstructor() 返回的是默认的构造器
				parameterTypes:是按声明顺序标识该方法形参类型的Class对象的一个数组
		public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
			获得Class所表示类中指定的构造器,和访问权限无关
		public Constructor<?>[] getConstructors()
			获得Class所表示类中所有用public修饰的构造器
		public Constructor<?>[] getDeclaredConstructors()
			获得Class所表示类中所有的构造器,和访问权限无关

		注:一个类的默认构造器的访问权限和类的访问权限一致

	获得普通方法:			
		public Method getMethod(String name, Class<?>... parameterTypes)
			获得Class所表示类中用public修饰的指定方法
				name:用于指定所需方法的简单名字
				parameterTypes:是按声明顺序标识该方法形参类型的Class对象的一个数组
		public Method[] getMethods()
			获得Class所表示类中所有用public修饰的方法,包括继承过来的方法
		public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
			获得Class所表示类中指定的方法,不包括继承过来的方法
		public Method[] getDeclaredMethods()
			获得Class所表示类中所有的方法,不包括继承过来的方法
			
	获得字段:
		与获得普通方法的方式一模一样。
	
	获得内部类:
		public Class<?>[] getClasses() 
			得到Class所描述的类中所有用public修饰的内部类
		public Class<?> getDeclaredClasses()
			得到Class所描述的类中所有的内部类
		
		
5,使用反射创建对象:

	1)使用Class对象的newInstance方法:
		public T newInstance():创建此Class对象所表示的类的一个实例,相当于调用了表示类的默认构造方法
	2)使用Class对象获取指定的Constructor对象,再调用Constructor的newInstance()方法创建对象类的实例,即用指定的构造方法创建实例。
		1,得到一个指定的构造方法
		2,调用构造方法(Constructor)对象的newInstance(Object... initargs)方法
			public T newInstance(Object... initargs) 
				initargs表示构造器需要传入的实际参数

				
6,忽略访问权限(暴力反射)

	使用java.lang.reflect.AccessibleObject类中的setAccessible(boolean flag)方法,当flag为true的时候,就会忽略访问权限(自然也可访问到私有的成员)
	注:Field、 Method、Constructor都继承了AccessibleObject类

	
7,使用反射调用方法

	说明:每个Method的对象对应一个具体的底层方法。获得Method对象后,程序可以使用Method里面的invoke方法来执行该底层方法
	
	public Object invoke(Object obj, Object... args):对带有指定参数(args)的指定对象(obj)调用由此 Method 对象表示的底层方法
		obj - 调用底层方法的对象
		args - 用于方法调用的参数,一般使用 new Object[]{arg1,arg2} 的形式。
			调用带有可变参数的方法:
				要点:把可变参数当作数组参数。
				如:show(String... s)作为show(String[] s)调用;
					show(T... a)作为show(Object[] a)调用;
				
				说明:
					1)若可变参数元素类型是引用类型:
						JDK内部接收到参数之后,会自动拆包取出参数再分配给该底层方法,为此我们需要把这个数组实参先包装成一个Object对象或者把实际参数作为一个Object一维数组的元素再传递。
					2)若可变参数元素类型是基本类型:
						JDK内部接收到参数之后,不会拆包,所以可以不必再封装,不过封装了也不会错
					
				结论:不管基本类型还是引用类型都使用Object[]{arg1,arg2}封装一层,保证无误
				
				例子:使用反射执行Arrays类中的 public static <T> List<T> asList(T... a) {}方法(注:static后面的<T>:声明使用T来作为泛型)

					Class<Arrays> clazz = Arrays.class;
					// 把可变参数:T... a 当作数组参数:Object[] a
					Method m = clazz.getMethod("asList", Object[].class);
					
					Object value = m.invoke(null, new Object[]{new String[]{"1","2","3"}});
					System.out.println(value);
				
	注意:
		1)如果底层方法是静态的,那么可以忽略指定的obj参数,该参数可以为null
		2)如果底层方法所需的形参数为0,则所提供的args可以为:省略、null、或 new Object[]{}
		3)返回值:
			若底层方法的返回值是基本类型,则首先将其包装成对象,然后返回该对象
			若底层方法的返回类型为void,则返回null
			若底层方法返回的是数组对象,则方法返回该数组对象,注意:如果是基本类型的数组,数组元素没有进行包装,仍返回基本类型的数组对象

			注:可以使用java.lang.reflect.Array类的get方法,获得数组对象的值
				public static Object get(Object array, int index)
				说明:返回数组对象中指定索引元素的值。如果该值是一个基本类型的值,则自动将其包装在一个对象中。
				注意:如果指定的对象不是一个数组,将抛出IllegalArgumentException异常
				举例:
					for (int i = 0; i < Array.getLength(array); i++) {
						System.out.println(Array.get(array, i));
					}

					
8,使用反射操作字段:

	Field提供两组方法操作字段
		获得字段对象(Field)的值
			public Object get(Object obj):返回指定对象上此 Field 表示字段的值。如果该值是一个基本类型值,则自动将其包装在一个对象中。 
				obj:字段所属的对象,下同。
			public xxx getXxx(Object obj):获取obj对象该Field的字段值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用:Object get(Object obj);
			
		设置字段对象(Field)的值
			public void set(Object obj, Object value):将指定对象变量上此 Field 对象表示的字段设置为指定的新值。如果底层字段的类型为基本类型,则对新值进行自动解包。
			public void setXxx(Object obj,xxx val):将obj对象的该Field字段设置成val值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用:void set(Object obj, Object value);
			
		访问静态属性:

			获取静态属性的值:field.get(null) 或 field.get(obj)
			设置静态属性的值:field.set(null, string) 或 field.set(obj, string)



	Field提供的其他常用方法
		public String getName()
		返回此 Field 对象表示的字段的名称。

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

		public Type getGenericType()
		返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型



9,反射的优化:

	说明:
		通过反射的方式创建对象 			比 直接new对象 			慢几倍
		通过反射的方式访问对象的属性	比 直接访问对象的属性	慢几百倍
		通过反射的方式调用对象的方法	比 直接调用对象的方法	慢几百倍

	反射效率较低的原因:
		通过反射访问对象的属性或方法时,需要遍历该对象所有的属性或方法,这是反射效率低的主要原因。
		通过反射访问对象的属性或方法时,需要进行一些安全检查,以保证该类的属性或方法被安全的访问。
		
	优化:
		1>将Field、Method等对象(使用一个Map)缓存起来,访问时直接从缓存中获取Field、Method对象。
		2>在缓存Field、Method等对象前,忽略它们的访问权限:
			eg:
				// 将Field、Method等对象缓存起来
				Field nameField = Person.class.getField("name");
				Method helloMethod = Person.class.getMethod("hello");
			
				nameField.setAccessible(true);
				helloMethod.setAccessible(true);
			
				HashMap<String, Field> fieldMap = new HashMap<>();
				HashMap<String, Method> methodMap = new HashMap<>();
			
				fieldMap.put("Person_field_name", nameField);
				methodMap.put("Person_method_hello", helloMethod);
				
				// 访问属性或方法时直接从缓存中拿到Field、Method等对象
				fieldMap.get("Person_field_name").get(obj);			// 访问obj的name属性的值
				methodMap.get("Person_method_hello").invoke(obj);	// 调用obj的hello方法

	
	
	
	



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值