黑马程序员——Java反射

------- android培训java培训、期待与您交流! ----------

Java反射机制

一、概述

反射就是把Java类中的各种成分映射成相应的java类。例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个类。表示Java类的Class显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。


反射的基石-->Class 类
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。

Class类代表Java类,它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码。例如:Person类的字节码,ArrayList类的字节码,等等。一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?


如何得到各个字节码对应的实例对象(Class类型)?
类名.class。例如:System.class
对象.getClass()。例如:new Date().getClass();
Class.forName("类名")。例如:Class.forName("java.util.Date");

九个预定义Class实例对象
boolean、byte、char、short、int、long、float、 double 和 void
参看Class.isPrimitive()方法。
int.class == Integer.TYPE


数组类型的Class实例对象
Class.isArray();

总之,只要是在源程序中出现的类型,都有各自的Class实例对象。例如:int[],void

System.out.println(int.class.isPrimitive()); //true
System.out.println(int.class == Integer.class); //false
System.out.println(int.class == Integer.TYPE); //true

System.out.println(int[].class.isPrimitive()); //false
System.out.println(int[].class.isArray()); //true


Java类用于描述一类事物的共性。该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写class关键字的区别哦。Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的的包名,字段名称的列表。方法名称的列表,等等。







二、构造方法的反射应用

Constructor 类

Constructor 类代表某个类中的一个构造方法。


得到某个类所有的构造方法:
例子:
Constructor[] constructors = Class.forName("java.lang.String").getConstructors();

得到某一个构造方法:
例子:
Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);//获得方法时要用到类型

创建实例对象:
通常方式: String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));//调用获得的方法时要用到上面相同类型的实例对象。

Class.newInstance()方法:
例子: String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

		//new String(new StringBuffer("abc"));
		Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
		String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));
		System.out.println(str2.charAt(2)); //c


一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?根据参数的个数和类型。
例如:Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。
重点: 参数类型用什么方式表示?用Class实例对象。
例如: 
	int .class, (int[]).class
	int[] ints = new int[0];
	ints.getClass();

Constructor 对象代表一个构造方法,大家觉得Constructor对象上会有什么方法呢?得到名字,得到所属于的类,产生实例对象。


三、成员变量的反射

Field 类
Field 类代表某个类中的一个成员变量。
演示用eclipse自动生成Java类的构造方法。
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?
所以字段fieldX 代表的是X的定义,而不是具体的X变量。

示例代码:

	ReflectPoint类的定义:
		public class ReflectPoint {
			private int x;
			public int y;
			public ReflectPoint(int x, int y) {
				super();
				this.x = x;
				this.y = y;
			}
		}

		public static void main(String[] args) throws Exception{
			// TODO Auto-generated method stub
			ReflectPoint pt1 = new ReflectPoint(3, 5);
			Field fieldY = pt1.getClass().getField("y");
			//fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上的,要用它去取某个对象上对应的值。
			System.out.println(fieldY.get(pt1));
			
			Field fieldX = pt1.getClass().getDeclaredField("x"); //x是私有的,要用getDeclaredField()方法
			fieldX.setAccessible(true); //
			System.out.println(fieldX.get(pt1));
		}



三、成员方法的反射

Method 类
Method 类代表某个类中的一个成员方法。
得到类中的某一个方法:
例子:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);

调用方法:
通常方式: System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?
说明该Method对象对应的是一个静态方法。

jdk1.4 和 jdk1.5 的invoke方法的区别:
jdk1.5:public Object invoke(Object obj,Object...args )
jdk1.4:public Object invoke(Object obj,Object[] args ),即按jdk1.4的语法,
需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用jdk1.4 改写为charAt.invoke("str", new Object[]{1})形式。

			String str1="abc";
			Method methodCharAt = String.class.getMethod("charAt", int.class);
			System.out.println(methodCharAt.invoke(str1, 1)); //b
			System.out.println(methodCharAt.invoke(str1, new Object[]{2})); //c
		

大家应通过思考和推理的方式来学习反射中的API。例如:Class.getMethod方法用于得到一个方法,该方法要接受什么参数呢?
显然要一个方法名,而一个同名的方法有多个重载形式,用什么方式可以区分清楚想得到重载方法系列中的哪个方法呢?
根据参数的个数和类型,例如:Class.getMethod(name, Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。



四、对接收数组参数的成员方法进行反射

用反射方式执行某个类中的main方法
目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5 的语法,整个数组是一个参数,而按jdk1.4 的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,java会到底按照哪种语法进行处理呢?jdk1.5 肯定要兼容jdk1.4 的语法,会按  照jdk1.4 的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main 方法传递参数时,不能使用代码 mainMethod.invoke(null, new String[]{"xxx"})。javac只把它当作jdk1.4 的语法进行理解,而不把它当作jdk1.5 的语法解释,因此会出现参数类型不对的问题。

解决办法:
mainMethod.invoke(null, new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null, (Object)new String[]{"xxx"}); 编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。

示例代码:

		public class ReflectTest {
		
			public static void main(String[] args) throws Exception{
				String startingClassName = args[0];
				Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
				mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
			}
		}
		
		class TestArguments{
			public static void main(String[] args)
			{
				for(String arg : args)
				{
					System.out.println(arg);
				}
			}
		}



五、数组的反射应用

数组的反射
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。

基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。

Arrays.asList()方法处理int[]和String[]时的差异。

怎么得到数组中的元素类型?得不到
Array工具类用于完成对数组的反射操作。

示例代码:

		public class ReflectTest {
		
			public static void main(String[] args) {
				// TODO Auto-generated method stub
				String[] a6 = new String[]{"a","b","c"};
				printObject(a6);
				printObject("xyz");
			}
		
			private static void printObject(Object obj) {
				Class clazz = obj.getClass();
				if(clazz.isArray())
				{
					int len = Array.getLength(obj);
					for(int i=0; i<len; i++)
					{
						System.out.println(Array.get(obj, i));
					}
				}
				else
				{
					System.out.println(obj);
				}
			}
		}



六、反射的作用

反射的作用——>实现框架功能
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调。我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。
框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。

综合案例
先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashCode方法,比较两个集合的运行结果差异。
然后改为采用配置文件加反射的方式创建ArrayList和HashSet实例对象,比较观察运行结果差异。
引入了eclipse对资源文件的管理方式的讲解。

ReflectPoint类的定义:

		public class ReflectPoint {
			private int x;
			public int y;
			public ReflectPoint(int x, int y) {
				super();
				this.x = x;
				this.y = y;
			}
			
			@Override
			public int hashCode() {
				final int prime = 31;
				int result = 1;
				result = prime * result + x;
				result = prime * result + y;
				return result;
			}
		
			@Override
			public boolean equals(Object obj) {
				if (this == obj)
					return true;
				if (obj == null)
					return false;
				if (getClass() != obj.getClass())
					return false;
				ReflectPoint other = (ReflectPoint) obj;
				if (x != other.x)
					return false;
				if (y != other.y)
					return false;
				return true;
			}
		}

在eclipse的javaenhance工程文件中创建一个配置文件:config.properties,内容为:className=java.util.ArrayList

主程序代码:

		public class ReflectTest2 {
		
			public static void main(String[] args) throws Exception{
				// TODO Auto-generated method stub
				//反射
				
				/*
				 getRealPath();
				 一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。
				*/
				//InputStream ips = new FileInputStream("config.properties");
				
				//类加载器
				//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn\\itcast\\day01\\config.properties");
				//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day01/config.properties");
				//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
				
				//把配置文件config.properties放在cn\itcast\day01\resources文件夹里。
				//InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties"); //相对路径
				InputStream ips = ReflectTest2.class.getResourceAsStream("cn/itcast/day01/resources/config.properties"); //绝对路径
				
				
				Properties props = new Properties();
				props.load(ips);
				ips.close();
				String className = props.getProperty("className");
				Collection collections = (Collection)Class.forName(className).newInstance();
				
				
				/**Java中的集合(Collection)有两类,一类是List,再有一类是Set。*/ 
				//Collection collections = new ArrayList(); //元素是有序的,元素可以重复
				//Collection collections = new HashSet(); //元素无序,但元素不可重复
				ReflectPoint pt1 = new ReflectPoint(3,3);
				ReflectPoint pt2 = new ReflectPoint(5,5);
				ReflectPoint pt3 = new ReflectPoint(3,3);
				
				collections.add(pt1);
				collections.add(pt2);
				collections.add(pt3);
				collections.add(pt1);
				
				//pt1.y = 7; //如果没有这一句,结果为:1。(有这一句内存泄露)
				//collections.remove(pt1); //结果为:2
				
				System.out.println(collections.size());
				//ArrayList结果:4; HashSet结果:3,有了hashCode()和equals()方法之后,结果为:2.
			}
		}



七、JavaBean

JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名。如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
setId()的属性名——>id
isLast()的属性名——>last
setCPU()的属性名是什么?——>CPU
getUPS()的属性名是什么?——>UPS
总之,一个类被当作JavaBean调用时,JavaBean的属性是根据方法推断出来的,它根本看不到java类内部的成员变量。

一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean。
好处如下:
在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,则人都这么用和要求这么做,那你就没什么挑选的余地!
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的X,怎么做,有一定难度吧?用内省这套API操作JavaBean比用普通类的方式更方便。
示例代码:

		public class ReflectPoint {
			private int x;
			public int y;
			
			public int getX() {
				return x;
			}
			public void setX(int x) {
				this.x = x;
			}
		
			public int getY() {
				return y;
			}
			public void setY(int y) {
				this.y = y;
			}
		
			public ReflectPoint(int x, int y) {
				super();
				this.x = x;
				this.y = y;
			}
		}

		public class IntroSpectorTest {
		
			public static void main(String[] args) throws Exception{
				// TODO Auto-generated method stub
				ReflectPoint pt1 = new ReflectPoint(3, 5);
				String propertyName = "x";
				
				//"x"-->"X"-->"getX"-->MethodGetX-->
				//get
				Object retVal = getProperty(pt1, propertyName);
				System.out.println(retVal);
				
				//set
				Object value = 7;
				setProperty(pt1, propertyName, value);
				System.out.println(pt1.getX());
			}
		
			private static void setProperty(Object pt1, String propertyName,
					Object value) throws IntrospectionException,
					IllegalAccessException, InvocationTargetException {
				PropertyDescriptor pd2 = new PropertyDescriptor(propertyName, pt1.getClass());
				Method methodSetX = pd2.getWriteMethod();
				methodSetX.invoke(pt1, value);
			}
		
			private static Object getProperty(Object pt1, String propertyName)
					throws IntrospectionException, IllegalAccessException,
					InvocationTargetException {
				PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1.getClass());
				Method methodGetX = pd.getReadMethod();
				Object retVal = methodGetX.invoke(pt1);
				return retVal;
			}
		}

采用遍历BeanInfo的所有属性方式来查找和设置某个ReflectPoint对象的X属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。

示例代码:

		private static Object getProperty(Object pt1, String propertyName)
				throws IntrospectionException, IllegalAccessException,
				InvocationTargetException {

			BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
			PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
			Object retVal = null;
			for(PropertyDescriptor pd : pds)
			{
				if(pd.getName().equals(propertyName))
				{
					Method methodGetX = pd.getReadMethod();
					 retVal = methodGetX.invoke(pt1);
					break;
				}
			}
			return retVal;
		}

使用BeanUtils工具包操作JavaBean

Beanutils工具包
演示用eclipse如何加入jar包,先只是引入Beanutils包,等程序运行出错后再引入logging包。
在前面内省例子的基础上,用Beanutils类先get原来设置好的属性,再将其set为一个新值。get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串。
用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。

		//BeanUtils工具包
		//System.out.println(BeanUtils.getProperty(pt1, "x")); //7
		System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName()); //java.lang.String
		BeanUtils.setProperty(pt1, "x", "9"); //9
		System.out.println(pt1.getX());
		
		BeanUtils.setProperty(pt1, "birthday.time", "111");
		System.out.println(BeanUtils.getProperty(pt1, "birthday.time")); //111

		PropertyUtils.setProperty(pt1, "x", 9);
		System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName()); //java.lang.Integer
		


------- android培训java培训、期待与您交流! ----------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值