-------android培训、java培训、期待与您交流! ----------
一、什么是反射的基石
了解什么是反射前,需要知道反射的基石--Class类:
一个形象简化的例子:众多的人用一个Person类表示,那么众多的Java类就用一个Class类表示。
可以从对照类和其实例对象开始分析,如下:
1.1、在Java中,用于描述一类事物的共同属性和方法,称为Java类。比如Person类,用于描述人的共同属性和方法,包括人的name(姓名),age(年龄),speaking(说话)等。
1.2、具体到某一个事物,具备具体的属性值,就称为Java类的一个实例对象。比如Person类的一个实例对象“new Person()”,具备Person类中描述的所有的属性,并且具备属于这个对象的具体的属性值,如:String name ="WangWu",int age = 20等。
2.1、在Java程序中各个Java类都属于同一事物,可以用一个类来描述,这个类就是Class。这个Class主要描述类的名字、类的访问属性、方法、构造方法、以及类所属的包名,字段名称的列表等等。
2.2、在计算机中,一个Java类被类加载器加载到内存中,需要占用一片存储空间,这个空间里面的内容就是该类的字节码,不同类的字节码是不同的,所以它们在内存中的内容也是不同的,这一个个的空间分别用一个个的对象来表示,这些对象具有相同的类型,这个类型就是Class,Class类的构造方法是Private,无法直接new Class()。
2.3、所有Java类的字节码文件,都是Class类的实例化对象,这些字节码只会被装载一次。
总结:Class类就是用来描述所有的Java类的共同属性和方法的。
静态加载:
Person p1 = new Person();
Class cls1 = Person.class;
cls1:代表Person这个类在内存空间的东西,又叫字节码,是Class类的一个实例对象。
P1:代表ch1字节码搞出来的对象。
原因:Person类要创建它的一个实例对象,必须先将该类加载进内存,而内存中存储的都是二进制的字节代码,所以该类必须以字节的形式才能存储到内存空间,就有了该类的字节码。该类字节码存储后才会建立对象。
二、如何获得各个字节码相应的实例对象(Class类型)
|--类名.class,例如,System.class
//编译器一看到“类名.class”就知道要得到这个类的字节码
|--对象.getClass(),例如,new Date().getClass();
//这个对象肯定是一个字节码创建出来的,调用对象的getClass方法是为了得到创建此对象的字节码,这个字节码也是我们通常说的类。
|--Class.forName("类全名"),例如,Class.forName("java.util.Date");
//通过静态方法查询或者加载字符串所对应的那个类的字节码
Class.forName("类全名")的作用:
1、本身JVM里面已经缓存了字节码,就不需要加载了直接返回字节码
2、JVM里面没有这个字节码,需要用类加载器加载进内存,再返回字节码,下次就不需要加载了。
通过下面例子更好的理解获取类的字节码的三种方法:
运行结果:
true
true
总结:从运行结果看,JVM只会在内存中存储一份该类的字节码。
运行结果:
false
true
总结:可以用Class类的isPrimitive方法去判断字节码是什么数据类型(包括基本数据类型和引用数据类型或者类类型等等)。
三、九个预定义的Class类实例对象
在Class类中用了TYPE常量来定义Class类的实例对象,如下:
boolean.class==Boolean.TYPE;
char.class==Character.TYPE;
byte.class==Byte.TYPE;
short.class==Short.TYPE;
int.class==Integer.TYPE;
long.class==Long.TYPE;
float.class==Float.TYPE;
double.class==Double.TYPE;
void.class==Void.TYPE
运行结果:
false
true
false
true
总结:只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
四、什么是反射
概念:反射,就是Java类中各种成分映射(解析)成相应的Java类。简单说:反射,就是对一个类进行解剖,
反射(机制),是指在运行状态中,对于任意一个类文件 (字节码文件),都能够动态获取这个类自身所有的属性和方法以及动态调用该类对象中任意一个方法和属性。反射的好处:大大的增强了程序的扩展性。
Java类中的各种成分包括变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。下面画个图加深理解:
五、反射之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();
//该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
指定构造函数创建对象:
总结:字节码创建实例对象如果需要指定传递有参数的构造函数,可以使用上面的方式,一般情况JVM会默认空参数的构造函数。
六、反射之Field类
*概述:Field类代表某个类中的一个成员变量。
*得到某个类所有的字段:
例子:Field[] fields= Class.forName("java.lang.String").getFields();
*得到某一个字段:
例子:Field field = Class.forName(“java.lang.String”).getField("y");
*创建实例对象并获取字段信息:
例子(权限:public):
String obj = (String)Class.forName("java.lang.String").newInstance();
Field field = Class.forName(“java.lang.String”).getField("y");
System.out.println(field.get(obj));
例子(权限:private):暴力反射
String obj = (String)Class.forName("java.lang.String").newInstance();
Field field = Class.forName(“java.lang.String”).getDeclaredField("x");
field.setAccessible(true);
System.out.println(field.get(obj));
下面是代码示例:
用于反射的ReflectPoint类
用反射技术测试获取ReflectPoint类的字段信息的ReflectTest类
例题:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
这里还是用到上面的类和对象,只截取部分程序:
ReflectPoint类
ReflectTest类
运行结果:
ReflectPoint [str1=aall, str2=aasketaall, str3=itcast]
七、反射之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对象对应的是一个静态方法!
下面是代码示例
运行结果:
b
*dk1.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})形式。
代码示例:
运行结果:
b
c
八、如何用反射执行某个类的main方法
定义一个TestArgument类在ReflectTest类的同一编辑窗口里面:
要想得到TestArgument类的字节码,必须关联ReflectTest类得到,做法如下:
1、先找到TestArgument类,鼠标移到TestArgument类上点F2键,出现类名,Ctrl+C,如下图:
2、右击点(打开方式)Run As 选择(运行配置)Run Configurations,如下图:
把类名复制到程序参数对话框里面,即可!
注意:在参数配置对话框只写一次类名的话,说明该类名就是main方法中String[]数组的第一个元素,即args[0]
如果只想用args[1]表示类名,前面写null,后面写类全名即可,比如:null cn.itcast.day1.TestArguments
截取ReflectTest类中主函数部分代码,获取TestArguments类中的main方法如下:
总结:在一个类里面用反射执行另一个个类的main方法的好处,就是在运行配置的参数对话框里面存入另一个类的全名即可,不需要改动源代码。
九、关于数组的反射
什么样的数组具有同一份字节码?
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(即在内存中是同一份字节码)
代码示例:
如何获取数组的字节码的父类(即Object类对应的Class)?
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
代码示例:
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
代码示例:
Arrays.asList()方法处理int[]和String[]时的差异。
代码示例:
Array工具类如何用于完成对数组的反射操作。
代码示例:
运行结果:
[a, b, c]
a
b
c
十、利用反射进行ArrayList和HashSet的比较及Hashcode分析
分别定义三个对象,其中rp1和rp3的值相同。
1、当向ArrayList分别加入rp1,rp2,rp3,rp1时,再打印出集合的大小时结果是4。
2、当向HashSet分别加入rp1,rp2,rp3,rp1时,再打印出集合的大小时结果是3。
可以得出结论:HashSet不能重复装入同一对象两次,对象唯一。
3、当复写反射类的hashCode和equals方法后,打印结果却是2。
手动在ReflectPoint类中调用hashCode方法和equals方法,代码如下:
可以得出结论:复写后,两个对象的hashCode相同且equals也相同,这时集合就会判断他们为同一对象。
4、HashSet如果先加入对象后改变对象的值,再去移除该对象将会失败。这样会就产生内存泄露的问题。
可以得出结论:修改了HashCode值,导致JVM找不到原来的HashCode值标识的数据。以为已经删除的数据实际上还
存在就造成了内存泄漏(已经没有用的数据还没有被释放一直占用内存空间,浪费资源,这种现象就是内存泄漏)
十一、反射的作用-->实现框架功能
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。
框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
1、我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
2、因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
综合案例
1、先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashcode方法,
比较两个集合的运行结果差异。
2、然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。
3、引入了elipse对资源文件的管理方式的讲解。
代码示例: