反射

 

  类都有自己的构造方法,成员变量,成员方法。成员都有自己的修饰符,注解信息等。而每一个类的方法都各自不同。如果将类看做是一个对象实体,那么该类所拥有的这些成员变量和成员方法就可以已数据化的形式抽取出来。类A有构造函数,类B也有构造函数,两个构造函数完全不同,但他们同属构造函数这一名词,如果将类看做是一个对象,那么不管是类A还是类B,他们的构造函数又可以用一个类来表示,即构造函数类,而每一个构造函数都是该类的一个实例对象。在不同构造函数之间的切换只需要改变所属的类的Class对象即可。同理,其它方法和属性也是如此。

 

  将类本身实例化对象并抽取出该类的各种属性和方法将这些属性和方法等再次封装成已经规定好的类型的实例,这个功能就是Java中的反射。换句话说,Java类中所有的元素都可以以对象的形式被封装。

 

  函数指针在C/C++中充当着各种动态编译牵引线,面向对象的多态性与模板在函数指针的配合下更加的随心所欲,灵活性极强。在Java中为了代码的健壮和易学完全屏蔽了对内存地址的一切操作可能。指针的消失也同样意味着函数指针的消失,这使的Java在面对动态化编程时束手束脚,然而反射弥补了这一缺憾,甚至有过之而无不及。通过反射,程序员可以很容易的拿到一个类的某个方法,即使在这之前你并不知道该类最终运行时会是谁。

 

  每一个类被实例化后都对应唯一一个Class对象。所有类实例化为对象后这些被实例化的对象统归为一个Class类。Class即是类的类。


类的对象表现形式:

 

Class并没有提供构造函数,获取它的实例可以通过以下三种方式:

//使用Class类的静态方法,参数以字符串形式指定完整的包含包名的类名
public static Class<?> forName(String className)

//通过Object提供给所有类的共用方法,返回当前调用对象所属类的Class对象实例
public final Class<?> getClass()

//由类名调用关键词class获取,该class似乎是Object中的静态成员变量,记录自身所属类的Class对象。
Class intCls = Integer.class;


 
  八个基本类型数据甚至包括void,都拥有他们自己的Class对象,基本数据包装类不属于基本类型,但每个基本数据包装类内都有一个静态成员TYPE,该静态成员记录的是所属类所包装的基本类型的Class对象。

 

  如:int.class == Integer.TYPE,结果为true。可以用isPrimitive来判断该Class对象是否是一个基本数据类型。

 

  void属于基本数据类型,所有数组都不属于基本数据类型。可以使用Class的isArray方法判断一个Class对象是否是数组类型。

 

  Class类还提供了一组判断方法,用来判断当前Class对象是哪种类型,如数组,枚举,接口,复合类,匿名类,本地类,成员类等等。

 

构造函数的对象表现形式:

 

Class提供了相应提取构造方法的功能。

//按照指定的参数类型映射出相应的构造方法,构造方法只能是public修饰的
public Constructor<T> getConstructor(Class<?>... parameterTypes)

//映射该Class对象所代表的类的所有public构造方法到一个数组中。
public Constructor<?>[] getConstructors()

 

  Constructor类的每一个对象代表着一个构造方法。因为默认构造方法的权限同类本身一样,如果类是public的并且没有指定任何构造方法,那么返getConstructors回一个默认构造方法。

 

  因为枚举无法创建新实例,所以getConstructors此方法返回的数组始终是0长度,数组以及所有基本数据类型都没有构造方法。

 

  通过Constructor对象可以构造一个该对象代表的构造方法所在类的新实例,方法如下:

//该行获取类Reflection带两个参数的构造方法,参数为一个int一个String。
Constructor<Reflection> constructor = Class.forName("packname.Reflection").getConstructor(int.class,String.class);

 //使用该构造方法创建一个新的Reflection类对象,需要为其传递对应的实际参数。   
Reflection r = constructor.newInstance(5,"str");



  getDeclaredConstructor与getDeclaredConstructors方法强制提取Class对象所代表类的所有构造方法,并且可以通过得到的Constructor对象使用私有构造方法构建对象,但在这之前必须调用Constructor对象的setAccessible方法设备参数为true。

 

  这设定打破了Java完美的封装性和安全机制。但相对来说使的编程中灵活中也高了很多,做为用习惯了指针的人来看,这应该是个不错的设置。

 

  Class本身也有一个newInstance()该方法不带参数,调用后会创建一个Class对象所代表类的实例,但只能通过该类的无参构造方法创建。如果该类没有无参构造方法,会产生InstantiationException异常

 

 

成员变量的对象表现形式:

 

  得到一个类的Class对象表现形式后可以用以下方法获取该类内的成员变量。

//按照指定成员变量名的字符串表现形式映射该成员变量,该成员只能是public修饰的包含静态成员变量与常量
public Field getField(String name)

//映射出Class对象所表示类的所有public成员变量,包含静态成员变量与常量
public Field[] getFields()



  在拿到成员变量所映射出的Field对象后,可以通过这个对象对成员变量做获取与修改操作。

//根据一个成员变量所在实例对象获取它的值,值以Object类型返回,之后通过toString可以将该值换为字符串
public Object get(Object obj)

//根据一个成员变量所在实例对象修改它的值,第二个参数为要设置的新值,
public void set(Object obj, Object value)


 

  由于成员变量会随着对象的不同而不同,因此对成员变量的操作时必须指定该成员变量所属于哪个实例对象。在对静态成员变量设置或获取时可以将Obj参数设置为null。

 

  另外还提供了两组八种基本类型专用的set与get方法,用于已明确了变量类型时获取与修改该变量,仅只为省去转换的麻烦,用处不大。

 

  通过getType()方法可以返回该Field表示的成员变量是属于class类型。即field.getType()返回的是该Field表示的成员变量所属类型的那个Class对象。

 

  同Constructor一样,Field提取也有相对应的getDeclaredField与getDeclaredFields方法,即强行提取非公有成员变量,调用setAccessible(true)之后就可以突破封装性,从外界强制访问或修改该类中的私有成员。好在无法对修饰了final的变量修强行修改,否则我真要怀疑反射对Java语言的健壮影响。不过让我非常奇怪的是,Declared方法获取从父类继承而来的成员变量时报NoSuchFieldException,那怕这个变量是public的也不行,而getFields却可以得到父类继承而来的public成员变量,不知道这算不算是反射的一个BUG。

 

  在C++中从一个对象的头地址处向后有规律的移动指针,将指针定位到该对象内部的私有成员处,然后对该地址处的数据越权修改,这一现象一直被认为是C++面向对象封装性最差的地方。现看Java反射却故意搞出这么个玩意来。可以访问修改私有成员,虽然提高了灵活性,但安全性却大大折扣。

 

 

成员方法的对象表现形式:

 

  得到一个Class实例对象后,可以通过的以下方法获取当前Class对象代表的那个类的成员方法。

//参数1是方法名,参数2是该方法的多个参数类型。如果方法没有任何参数可以不指定第二个参数,或是用null代替。
public Method getMethod(String name, Class<?>... parameterTypes)

//获取对应的Class实例对象中所有的public成员方法,返回的数组中每一个Method映射一个成员方法。
public Method[] getMethods()

 

  Method的获取方法与Constructor大相径庭,仅多一个区别不同名称的参数。参数1用于区分不同的方法,参数2用于区分重载方法。

 

  得到一个成员方法所映射的Method对象后,可以通过method.invoke调用这个成员方法。

public Object invoke(Object obj,Object... args)

 

  参数1为方法所属类的实例对象,静态方法可使用null,如同Field的set方法参数1一样。参数2为一个可变参数,用来指定调用该Method所代表的方法时为其传递的实际参数值。如果实际参数为空可以不指定或使用null。而返回值如同实际调用该函数一样得到相应的返回值,但返回值以Object类型返回。

 

  在没有可变参数之前,使用数组接收一个多参数,由于为了兼容以往版本保留了这种使用方法,即传递数组相当于传递了一个多参数。换句话说在调用invoke时第2个参数无论是数组还是多个参数,在invoke内部全部以多个参数的形式将传递进来的行参解析。这样以来就引发一个,假如一个函数本身的参数类型就是一个数组,那么通过反射的invoke调用它时,为它传递的数组参数会被编译器无情的解析为多个参数,最终导致参数类型不正常。为了解决这个问题,可以将传递进去的数组类型强制转换成Object(因为数组本身也是一个类它派生自Object),这样数组就会被识为一个Object的对象,而不是一个数组。 

 

  需要注意的是,如获取Field时一样,相应获取Method的getDeclaredMethod与getDeclaredMethods方法也不可以拿到继承而来的任何方法,而getMethod与getMethods方法,却可以拿到public修饰的基类成员方法。好大的BUG。


附带一个反射实例,使用反射改变对象中所有参数中包含的数字:

import java.lang.reflect.*;

public class MainClass {
	
	public static void main(String[] args)throws Exception{
		//得到类的字节码类
		Class<ReflectPoint> clsArgs = ReflectPoint.class;
		//获取该类的带参构造方法
		Constructor<ReflectPoint> constructor1 = clsArgs.getConstructor(
				int.class,int.class,int.class,int.class,int.class);
		//反射创建一个有参实例
		ReflectPoint onePoint = (ReflectPoint)Class.forName("packageName.ReflectPoint")
		.getConstructor(int.class,int.class,int.class,int.class,int.class).newInstance(551, 231, 425, 435, 576);
		//打印
		System.out.println(onePoint.toString());
		//获取变量对象
		Field[] fields = clsArgs.getDeclaredFields();
		//迭代
		for (Field field : fields) {
			//符合int型
			if (field.getType() == int.class) {
				//将每个参数的值以string形势取出
				String str = field.get(onePoint).toString();
				//替换其中的内容
				str = str.replace('5', '9');
				//将字符串转为Integer
				Integer num = Integer.valueOf(str);
				//将新数据设置回对应对象的变量中
				field.set(onePoint, num);
			}
		}
		//打印
		System.out.println(onePoint.toString());
	}
}
class ReflectPoint {
	public int num1;
	public int num2;
	public int num3;
	public int num4;
	public int num5;

	public ReflectPoint(int num1, int num2, int num3, int num4, int num5) {
		this.num1 = num1;
		this.num2 = num2;
		this.num3 = num3;
		this.num4 = num4;
		this.num5 = num5;
	}
	@Override
	public String toString() {
		return "ReflectPoint [num1=" + num1 + ", num2=" + num2 + ", num3=" 
		+ num3 + ", num4=" + num4 + ", num5=" + num5 + "]";
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值