黑马程序员——高新技术---反射

时间:2023-02-17 22:39:42
------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

反射

一、概述
    反射的基石-->Class类
Java程序中的各个类属于同一类事物,描述这一类事物的Java类名就是Class。
对比提问:众多的人用一个什么的类表示?众多的Java类用一个什么类表示?
人-->Person   Java类-->Class
Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性值是什么 ,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个类,他们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?而这个类的名字就是Class,要注意与小写class关键字的区别。
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
对比提问:Person类代表人,他的实例对象就是张三李四这样一个具体的人,Class类代表Java类,它的各个实例又分别对应什么呢?
    1、对应各个类在内存中的字节码,类如:Person类的字节码,ArrayList的字节码,等等
2、一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不同的,这一个个的空间分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
这个类型就是分别对应的类的字节码,作用,就是返回字节码
如何得到各个字节码对应的实例对象(Class类型)
     1、类名.Class,例如:System.Class
2、对象.getClass(),例如,new Date().getClass()
3、Class.forName("类名"),例如Class.forName("java.lang.String");//静态源程序多用该方法,因为不知道类名
作用:返回字节码,返回的方式有两种:
第一种:这份字节码曾经加载过,已经存在虚拟机里了,直接返回
第二种:虚拟机没有这段字节码,用类加载器去加载,然后缓存到虚拟机中。 
九个预定义Class实例对象
    基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
   <**>int.class==Integer.TYPE
数组类型的Class实例对象
Class.isArray()
总之,只要是在源程序中出现的类型,都具有各自的Class对象,例如,int[],void...
*****************************************************************************
反射定义:
"反射就是把Java类中的各种成分映射成相应的Java类。"例如:一个Java类中用Class类的对象来表示,一个类中的组成部 分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车一个类,汽车中的发动机,变速箱等等也 是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这 些信息就是用相应类的实例对象来表示,他们是Field、Method、Contructor、Package等等。
<span style="font-size:18px;">import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.Arrays;public class ReflectTest {	public static void main(String[] args)throws Exception {		// TODO Auto-generated method stub		String str1 = "abc";		Class cls1  =str1.getClass();		Class cls2 = String.class;		Class cls3 = Class.forName("java.lang.String");		//以上三句指向的同一个一个字节码		System.out.println(cls1==cls2);//true		System.out.println(cls1==cls3);//true//		判定指定的 Class 对象是否表示一个基本类型。		System.out.println(cls1.isPrimitive());//false,String 不是基本数据类型而是一个类//		判断int是否是基本数据类型		System.out.println(int.class.isPrimitive());//		不是同一个类型,字节码不同		System.out.println(int.class==Integer.class);//		Integer.TYPE包装基本数据类型,包含int,所以为true		System.out.println(int.class==Integer.TYPE);//		int[]也是一种类型,判断是否是数组。		System.out.println(int[].class.isArray());</span>



二、Constructor类
Constructor类代表某一个类中的一个构造方法
1、得到某个类所有的构造方法
例子:Constructor[] constructor=Class.forName("java.lang.String").getConstructor();
2、得到某一个构造方法:
例子:Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
//获得方法时要用到类型,不能传入该类型第几个构造函数,因为没有顺序的
3、创建实例对象:
通常方法:String str=new String(new StringBuffer("abc"));
反射方法:String str=(String)constructor.newInstance(new StringBuffer("abc"));
//调用获得的方法时要用到上面相同类型的实例对象。
4、Class.newInstance()方法:得到不带参数的构造方法
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象
<span style="font-size:18px;">接以上代码</span>
<span style="font-size:18px;">//		Constructor类实现下面这句话		//new String(new StringBuffer("abc"));		Constructor constructor = String.class.getConstructor(StringBuffer.class);//		编译是只是看右边,不知道是什么类型,所以要强转.通过newInstance创建新的实例对象		String str = (String)constructor.newInstance(new StringBuffer("abc"));				System.out.println(str.charAt(2));//c</span>



三、field类
field类代表某个类中的一个成员变量
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldX代表的是X的定义,而不是具体的x变量。
示例代码
//		Field类		ReflectPoint pt1 = new ReflectPoint(3,5);		Field fieldY = pt1.getClass().getField("y");//		fieldY的值是多少?5,错!fieldY不是对象身上的变量,而是类上,代表字节码产生的变量,要用它去取某个对象上对应的值		System.out.println(fieldY.get(pt1));		//		因为x是私有的,所以用一下方法调用		Field fieldX= pt1.getClass().getDeclaredField("x");//		暴力反射		fieldX.setAccessible(true);//允许访问		System.out.println(fieldX.get(pt1));						changeStringValue(pt1);		System.out.println(pt1);
//	练习:将任意一个对象中的所有String类型的成员变量所对应的字符串中的“b”改成“a”	public static void changeStringValue(Object obj)throws Exception	{		Field[] fields = obj.getClass().getFields();		for(Field field : fields)		{//			因为是同一个字节码所以要用==			//field.getType().equals(String.class)			if(field.getType()==String.class)			{				String oldValue= (String)field.get(obj);				String newValue=oldValue.replace('b','a');				field.set(obj,newValue);			}		}		}


public class ReflectPoint {		private int x;		public int y;		public String str1="ball";		public String str2="basketball";		public String str3="itcast";		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;		}		@Override		public String toString()		{			return str1+":"+str2+":"+str3;		}	}




四、Method类
Method类代表某个类中的一个成员方法
1、得到类中的某一方法:
例子:得到String类中的charAt(int index) 返回指定索引处的 char 值。
       Method charAt=Class.forName("java.lang.String").getMethod("charAt",int.class)
2、调用方法:
通常方式:System.out.println(str.charAt(1));
反射方法:System.out.println(charAt.invoke(str,1));
如果传递给Method对象的invoke()方法的第一个参数null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法
3、jdk1.4和jak1.5的invoke方法的区别:
jdk1.5:public Objectinvoke(Object obj,Object... args)
jdk1.4:public Objectinvoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数 传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可 以用jdk1.4改写为charAt("str",new Object[]{1})形式。
4、应用
用反射方式执行某个类中main方法
目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
//		Method类:str1.charAt(1);		Method methodCharAt = String.class.getMethod("charAt",int.class);//		jdk1.5		System.out.println(methodCharAt.invoke(str1, 1));	//		jdk1.4		System.out.println(methodCharAt.invoke(str1, new Object[]{1}));				//写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法//		普通方法:TestArgument.main(new String[]{"111","222","333"});		反射方法:		String startingClassName = args[0];		Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);		//静态的方法不需要传递参数//		mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});		mainMethod.invoke(null,(Object)new String[]{"111","222","333"});

class TestArgument{	public static void main(String[] args)	{		for(String arg : args)		{			System.out.println(arg);		}	}}




普通方式掉完后,为什么用反射的方法调用呢?
问题:启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射的方式来调用main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,再给main方法传递参数时,不能使用代码
mainMethod.invoke(null,new String[]{"xxx"}),java只把它当作jdk1.4的语法进行理解,而不是把它当作jdk1.5的语法理解,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}));,编译器会做特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干个参数了。
四、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
2、代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。
3、基本类型的一维数组可以被当做Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做object类型使用又可以当作Object[]类型使用。
4、Arrays.asList()方法处理int[]和String[]时的差异。
int是基本数据类型,传入asList(Object[] obj)的是int[]整体
5、Array工具类用于完成对数组的反射操作。
//		数组的反射		int[] a1 = new int[]{1,2,3};		int[] a2 = new int[4];		int[][] a3=new int[2][3];		String[] a4 = new String[]{"a","b","c"};//		数组的维数相同,类型形同,则他们的字节码相同		System.out.println(a1.getClass() == a2.getClass());//true//		System.out.println(a1.getClass() == a4.getClass());false//		System.out.println(a1.getClass() == a3.getClass());false		System.out.println(a1.getClass().getName());//[I代表int类型数组		System.out.println(a1.getClass().getSuperclass());//Object		System.out.println(a4.getClass().getSuperclass());				Object aObj1 = a1;		Object aObj2 = a4;//		int类型(基本数据类型)的一维数组不能转换为Object数组//		Object[] aObj3 = a1;false//		a3可以理解为Object[]中装的一维数组		Object[] aObj4 = a3;		Object[] aObj5= a4;		System.out.println(a1);		System.out.println(a4);		System.out.println(Arrays.asList(a1));		System.out.println(Arrays.asList(a4));		//		Array类的演示	//	Object obj = null;		printObject(a4);		printObject("xyz");


//	数组的反射	public 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);		}	}




五、HashCode
    产生原理:
    如果想找一个集合中是否包含某个对象,大概的程序代码怎么写呢?通常是取出每个元素与要查找的对象进行比较,当发现元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息。否则,返回否定的信息。如果一个集合中有很多元素,譬如一万个元素,并且没有包含要找的对象时,则意味着程序需要从该集合中去出一万个元素进行逐一比较才得到结论。所以有人发明了哈西算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以讲哈希码分组,每组分别对应某个寻出区域,根据一个对象的哈希码就可以确定该对象应该存储到那个区域。
只有类的实例对象要被采用哈希算法进行存储和检索时,这个类才需要按要求覆盖hashCode方法。及时程序可能暂时不会用到当前类的hashCode方法也不会有什么不好,没准以后什么时候用到这个方法了,所以,通常需求hashCode方法和equals方法一并被同时覆盖。
    提示:
    1、通常来说,一个类的实例对象用equals()方法比较结果相等时,他们的哈希码也必须相等,但反之则不成立,即equals方法比较结果不相等的对象可以有相同的哈希码,或者说哈希码相同的两个对象的equals方法比较的结果可以不想等,例如,字符串的"BB"和"Aa"的equals方法比较结果肯定不相等,但他们的hashCode方法返回值却相等。
    2、当一个对象被存储进HashSet集合后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了,在这种情况下,及时在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄漏。
    内存泄露就是内存中的某个数据不再使用,但是没有被释放,容易产生内存溢出。
 


六、反射的作用——实现框架功能
1、框架与框架要解决的核心问题:
我做房子卖给用户住,有用户安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户调用,而框架则是调用用户提供的类。
2、框架要解决的核心问题
a.我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样才能调用到 你以后写的类(门窗)呢?
b.因为在写程序时无法知道被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射 方式来做。
3、综合案例
a.先直接用new语句创建ArrayList和HashSet的实例对象,演示Eclipse自动生成ReflectPoint类的equals和 hashCode方法,比较两个集合运行结果差异。
b.然后改为采用配置文件将反射的方式创建ArrayList和HashSet的实例对象,比较贯彻运行结果差异。
c.引入了eclipse对资源文件的管理方式的讲解。
import java.io.FileInputStream;import java.io.InputStream;import java.util.*;public class ReflectTest2{	public static void main(String[] args)  throws Exception{		// TODO Auto-generated method stub//		尽量面相父类或接口		InputStream ips = new FileInputStream("config.properties");		//Properties是Map集合的子类,具有键值对		Properties props = new Properties();//		 从输入流中读取属性列表(键和元素对)。		props.load(ips);		ips.close();
<span style="white-space:pre">		</span>//className=java.util.HashSet;		String className = props.getProperty("className");		Collection collections =(Collection) Class.forName(className).newInstance();		 		//与反射效果相同的普通方法//		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);				System.out.println(collections.size());	}}


配置文件:
 className=java.util.HashSet
可以通过更改配置文件来改变存储方式,排序方式