------- android培训、java培训、期待与您交流! ----------
反射:
反射的基石->Class类
Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个java类。它们是否属于同一类事物,是不是可以用一个类来描述呢?这个类的名字就是Class,要注意与小写class关键字区分开来。Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属的名,字段名称的列表、方法名称的列表,等等。
对比提问:Person类表示人,他的实例对象就是张三,李四这样一个个具体的人,Class类代表java类,他的各个实例对象对应各个类在内存中字节码。
例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容不同,这一个个的空间可以分别用一个个的对象来表示。
反射就是把java类中的各种成分映射成相应的java类。
例如,一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像小汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class显然就要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的事例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
如何得到各个字节码对应的实例对象(Class类型)
通过下面三种方法获取
1、类名.class 如:System.class
2、对象.getClass() 如:new Date().getClass()
3、Class.forName(“类名”) 如:Class.forName(“java.util.Date”) 这是Class自己的静态方法获取字节码,必须是完整类名传入。
不允许Class cls = new Class(); Class类根本就没构造方法。
Class类的实例对象 代表内存里的一份字节码 每一个字节码都是一个Class的实例对象
九个预定义Class实例对象:
八个基本数据类型和void都分别对应一个Class实例对象
如,int.class ==Integer.TYPE
数组类型的Class实例对象
得到Class实例对象后可以调用Class中的方法Class.isArray()
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void...
例如:
String str1 = "abc";
Class cls1 = str1.getClass();//String类对象调用getClass()获取String类字节码文件
Class cls2 = String.class; //通过类名.class 的方式获取String类字节码文件
Class cls3 =Class.forName("java.lang.String"); //通过Class类的静态方法forName获取指定包名下的类文件字节码文件
System.out.println(cls1 == cls2);//一个类只有一份字节码文件,在内存里是唯一的,Class引用变量指向的都是同一个字节码文件对象,因此不管哪种方式获取的同一个类的字节文件都是一样的,输出true
System.out.println(cls1 == cls3);//同上,输出true
System.out.println(int.class.isPrimitive());//int型是基本数据类型,Class调用isPrimitive判断是否是基本数据类型。输出true
System.out.println(int.class ==Integer.class);//int是基本数据类型,Integer是int类型的包装类类型,输出fasle
System.out.println(int.class ==Integer.TYPE); // TYPE 是Integer里面预定义的一个常量,Integer . TYPE表示基本类型int 的 Class 实例,输出true
/*
八个基本数据类型 和void的包装类都有TYPE预定义常量来代表各自的基本数据类型的字节码
*/
System.out.println(int[].class.isPrimitive());//数组类型不是基本数据类型,输出fasle
System.out.println(int[].class.isArray()); //判断是否是数组类型,输出true
Constructor类
Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
Constructor [] constructors =Class.forName("java.lang.String").getConstructors();
得到一个指定参数类型的构造方法:
Constructor constructor =Class.forName("java.lang.String").getConstructors(StingBuffer.class);
创建实例对象:
通常方式:String str = new String(new StringBuffer(“abc”));
反射方式:String str =(String)constructor.newInstance(newStringBuffer("abc"));
Class.newInstance()方法:
例子:String obj = (String)Class.forName(“java.lang.String”).newIstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
//newString(new StringBuffer("abc")); //根据String类中带StringBuffer参数类型的构造函数创建一个String类对象。通过反射的方式可以完成这一动作。
Constructorconstructor1 = String.class.getConstructor(StringBuffer.class);//得到String类字节码文件中参数类型为StringBuffer的构造函数对象。
Stringstr2 = (String)constructor1.newInstance(/*"abc"*/newStringBuffer("abc"));//根据获取的String类构造函数对象创建String类对象实例,参数类型必须为StringBuffer类型,由于返回类型是Object类型的,因此还要强制转换成String类型。
System.out.println(str2.charAt(2));//调用String类中charAt方法,输出c。
Field类
代表某个类中的一个成员变量。
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多少个,如果是与对象关联,那关联的是哪个对象呢?
字段fieldX代表的是X的定义,而不是具体该类某一对象的X变量。
示例代码:
//定义ReflectPoint类
class ReflectPoint {
privateDate birthday = new Date();
privateint x; //私有成员变量
publicint y;
publicString str3 = "itcast";
//构造函数
ReflectPoint(intx, int y) {
super();
this.x= x;
this.y= y;
}
}
public calss ReflectTest{
publicstatic void main(String[] args) throws Exception{
ReflectPoint pt1 = new ReflectPoint(3,5);// 创建ReflectPoint类对像
FieldfieldY = pt1.getClass().getField("y");//通过ReflectPoint类字节码文件获取ReflectPoint类中公有成员变量y。
//fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值
System.out.println(fieldY.get(pt1));//fieldY.get(pt1)获取p1对象上y的值
/*私有成员变量的获取:
私有成员变量不能直接获取。要通过getDeclaredField方法来获取,
getDeclaredField是获取所有的成员变量,包括私有的成员变量,
还可以指定获取哪个成员变量,如getDeclaredField("x");获取x成员变量。
但获取后不能对其进行操作,还要通过取消私有字段权限检查才能操作,称为
暴力反射
*/
FieldfieldX = pt1.getClass().getDeclaredField("x");//获取x成员变量
fieldX.setAccessible(true);//取消权限检查
System.out.println(fieldX.get(pt1));//打印p1对象x的值。
}
}
作业:将任意一个对象的所有String类型的成员变量所对应的字符内容中的“b”改成“a”
代码演示:
//ReflectPoint类
calss ReflectPoint{
publicString str1=”ball”;
publicString str2=”basketball”;
publicString str3=”itcast”;
}
public calss ReflectTest{
publicstatic void main(String[] args){
ReflectPointpt1 = new ReflectPoint();
changeStringValue(pt1); //主函数调用静态方法
System.out.println(pt1);
}
//定义changeStringValue静态方法。
private staticvoid changeStringValue(Object obj) throws Exception {
Field[] fields =obj.getClass().getFields(); //获取obj对应的字节码文件所有的成员变量,返回一个数组。
for(Field field : fields){遍历该数组
//if(field.getType().equals(String.class))
if(field.getType() ==String.class){//判断遍历到的成员变量是否是字符串类型
String oldValue =(String)field.get(obj);//获取遍历到的成员变量值,需要强转动作
String newValue =oldValue.replace('b', 'a');//用字符串替换函数把oldValue字符串中所有b字符改成a字符并把返回的新字符串对象地址值赋给newValue引用
field.set(obj,newValue); //设置obj上遍历到的成员变量值为替换过的newValue
}
}
}
}
Method类:
Method类代表某个类中的一个成员方法。
得到类中的某一个方法:
例子:
Method charAt = Class.forName(“java.lang.String”).getMethod(“charAt”,int.class)
charAt是方法名,int.class是int型字节码文件对象参数,表示得到一个charAt方法参数类型为int型的函数,getMethod方法的参数是可变参数。
调用方法:
通常方式:str.charAt(1)
反射方式:charAt.invoke(str,1) //第一个参数表示该类对象,后面的参数表示获取到方法的参数列表,对应getMethod方法后面(第二个参数开始)的参数列表
如果传递给Method对象的invoke()方法的的第一个参数为null,说明该Method对象对应的是一个静态方法。
代码示例:
Stringstr1=new String(“abc”);//创建String类对象把地址值赋给str1引用。
Method methodCharAt = String.class.getMethod("charAt",int.class);// 得到String类字节码文件中有一个参数类型为int的charAt方法
System.out.println(methodCharAt.invoke(str1,1));//调用invoke方法执行该方法,执行对象是str1指向的对象,相当与str1.charAt(1),输出b
System.out.println(methodCharAt.invoke(str1,new Object[]{2}));//jdk1.4需要将一个数字作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,new Object[]{2}对应2.
对接收数组参数的成员方法进行反射
用反射方式执行某个类中的main方法(main方法中的参数是数组类型的):
目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的方法。
问题:
启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射的方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,会按照jdk1.4的语法进行处理,即把数组打散成若干个单独的参数。所以,给main方法传递参数时不能使用代码mainMethod.invoke(null,newString[]{“123”}),java只会把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法进行理解,因此会出现参数类型不对的问题。
解决办法:
第1种:mainMethod.invoke(null,new Object[]{new String[]{“123”}})
第2种:mainMethod.invoke(null,(String) new String[]{“123”}),编译器会特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
代码示例:
//定义TestArguments类
classTestArguments{
public static void main(String[] args){
for(String arg : args){//循环遍历main函数传递过来的数组参数
System.out.println(arg);
}
}
}
publiccalss ReflectTest{
publicstatic void main(String[] args){
//TestArguments.main(newString[]{"111","222","333"});//普通调用方法
//下面反射方式调用
String startingClassName = args[0];// args[0]表示传入主函数中的第一个参数
Method mainMethod =Class.forName(startingClassName).getMethod("main", String[].class);//获取指定包名下的类的字节码文件的main方法,参数为String[]
//mainMethod.invoke(null, new Object[]{newString[]{"111","222","333"}});//第一种方法执行
mainMethod.invoke(null,(Object)new String[]{"111","222","333"});//第二种方法执行
}
}
数组的反射:
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代码示例:
int [] a1 = new int[]{1,2,3};
int [] a2 =new int[4];
int[][] a3 = newint[2][3];
String [] a4= new String[]{"a","b","c"};
System.out.println(a1.getClass()== a2.getClass());//a1和a2是元素类型都是int类型的一维数组,获取的是同一份字节码文件,输出true。
System.out.println(a1.getClass() == a4.getClass());//a1和a4是元素类型不同的一维数组,获取的不是同一份字节码文件,输出false。
System.out.println(a1.getClass()== a3.getClass());a1和a3元素类型相同但维数不同,获取的不是同一份字节码文件,输出false。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
System.out.println(a1.getClass().getName());//获取字节码文件中的名字,输出[I。[表示是一维数组,大写字母I表示类型为int型
System.out.println(a1.getClass().getSuperclass().getName());//得到父类字节码文件的名字,输出java.lang.Object。
System.out.println(a4.getClass().getSuperclass().getName());//得到父类字节码文件的名字,输出java.lang.Object。
基本数据类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本数据类型的一维数组,即可以当作Object类型使用,又可以当作Object[]类型使用。
Object aObj1 = a1;//a1是数组对象,数组类继承Object,这里向上转型为Object类型
Object aObj2= a4;//同上
//Object[]aObj3 = a1;//错误,a1存放的是基本数据类型int型,不是对象,不能转成对象数组。
Object[]aObj4 = a3;//a3可以理解为一维数组再装一维数组,即元素类型为数组,数组是对象,把数组向上转型为Object类型。
Object[]aObj5 = a4;// a4存放的是字符串类型对象,可以转换成对象数组,即把数组存放的所有String类对象都向上转型为Object类型。
Arrays.asList()的方法处理int[]和String[] 时的差异。
System.out.println(Arrays.asList(a1));//打印[[I@1cfb549]
System.out.println(Arrays.asList(a4)); //打印[a,b,c]
出现这种情况的原因是:在jdk1.4中,asList方法的参数类型是元素为Object类型的数组,而到了jdk1.5,asList方法的参数类型是可变参数,序列作为数组的列表。并且可以用泛型指定类型。j dk1.5要兼容jdk1.4,会优先走jdk1.4中的方法。当传入String类型数组时,存放的是String类对象,因此会走jdk1.4中的asList方法,再把元素打散装入List集合中返回一个List集合。当传入int型数组时,由于int型是基本数据类型,不符合jdk1.4asList方法,因此走jdk1.5asList方法,jdk1.5中的方法会把整个int型数组当作一个参数,且不会打散,因为参数是可变的,当作只有一个参数,是int[]型的,打印时作为一整个数组打印hashCode值。
Array工具类用于完成对数组的反射操作
代码示例:
public calss ReflectTest{
publicstatic void main(String[] args){
String [] a4 = newString[]{"a","b","c"};//定义String类数组
printObject(a4);//主函数调用静态方法printObject
}
private staticvoid 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);
}
}
}
}
思考:怎么得到数组中的元素类型?
需要取出每个元素对象,然后再对各个对象进行判断,因为其中每个具体元素的类型都可以不同,例如:Object[] x=new Object[]{“abc”,Integer.Max}。
反射的作用->实现框架的功能
框架与框架要解决的核心问题:
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类是被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题:
我写框架(房子)时,你这个用户有可能还在上小学呢,还不会写程序呢,我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写框架程序时,无法知道要被调用的类名,所以,在程序中无法直接用new某个对象,而是要用反射的方式来做。
综合案例:
直接用new语句创建 ArrayList和HashSet的实例对象,演示用eclipse自动生成equals和hashcode方法,比较两个集合的运行结果差异
ReflectPoint类的定义:
class ReflectPoint {
privateint x;
publicint y;
publicReflectPoint(int x, int y) {
super();
this.x= x;
this.y= y;
}
@Override
//复写hashCode方法
publicint hashCode() {
finalint prime = 31;
intresult = 1;
result= prime * result + x;
result= prime * result + y;
returnresult;
}
@Override
//复写equals方法
publicboolean equals(Object obj) {
if(this == obj)
returntrue;
if(obj == null)
returnfalse;
if(getClass() != obj.getClass())
returnfalse;
finalReflectPoint other = (ReflectPoint) obj;
if(x != other.x)
returnfalse;
if(y != other.y)
returnfalse;
returntrue;
}
//eclipse自动生成set,get方法
publicint getX() {
returnx;
}
publicvoid setX(int x) {
this.x= x;
}
publicint getY() {
returny;
}
publicvoid setY(int y) {
this.y= y;
}
}
/*
ArrayList集合不具备比较性。即使元素有重复也会添加进集合。
HashSet集合具备比较性,为了实现元素的唯一性,使用了hashCode方法和equals方法来判断元素是否是同一个元素。当元素为自定义对象时,想要该对象具备比较性,必须要覆盖hashCode方法和equals方法,因为自定义对象中没有hashCode方法和equals方法
*/
public class ReflectTest2 {
publicstatic void main(String[] args) throws Exception{
//Collectioncollections = new HashSet();
Collectioncollections = new ArrayList();
ReflectPointpt1 = new ReflectPoint(3,3);
ReflectPointpt2 = new ReflectPoint(5,5);
ReflectPointpt3 = new ReflectPoint(3,3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
//pt1.y= 7; // pt1已经存储进Hashset集合了,hashCode值固定了,如果对y值进行修改,hashCode值就会改变,原因是y值参与了hash算法的运算。
//collections.remove(pt1);//不能删除pt1对象,因为删除要通过hashCode值进行检索,hashCode值改变后,检索到的元素并不是原先存进hashSet集合中的元素,有可能是空值,也有可能是其他元素。
/*
当一个对象被存储进Hashset集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。否则,对象修改后的哈希值与最初存储进hashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,从而造成内存泄漏。
*/
System.out.println(collections.size());
}
}
反射开发框架的原理
通过反射的方式来创建ArrayList和HashSet的实例对象
先创建一个配置文件:config.properties
配置文件的内容:calssName=java.util.ArrayList//指定要执行的类
代码示例:
public class ReflectTest2 {
publicstatic void main(String[] args) throws Exception{
InputStreamips = new FileInputStream("config.properties");//创建读取流对象
Propertiesprops = new Properties();
props.load(ips);//加载读取流关联的配置文件,这样props对象就得到了配置文件中的键值对信息。
ips.close();//关闭流对象,此时因为键值对信息已经被props对象得到了,及时关闭流防止内存泄漏。
StringclassName = props.getProperty("className");//得到 className键的值
Collectioncollections = (Collection)Class.forName(className).newInstance();//创建指定包名下的类的字节码文件的实例对象即ArrayList集合对象
//创建ReflectPoint对象
ReflectPointpt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPointpt3 = new ReflectPoint(3,3);
//collections集合添加ReflectPoint对象
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
System.out.println(collections.size());
}
}
用类加载器的方式管理资源和配置文件
public class ReflectTest2 {
publicstatic void main(String[] args) throws Exception{
/*getRealPath();//金山词霸/内部
一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。*/
//InputStreamips = new FileInputStream("config.properties");
/*
当类加载器加载class文件的时候,去classpath目录里面找到对应的calss文件并加载,所以类加载器加载文件的方法getResourceAsStream也是去classpath目录下寻找文件。而classpath根目录下是没有的,而是在classpath根目录下的cn/itcast/day1目录下才有配置文件,所以我们向getResourceAsStream()传入参数的时候要传入"cn/itcast/day1/config.properties"。这样就获取到配置文件的绝对路径了。
*/
//InputStreamips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");//通过ReflectTest2类的字节码文件得到类加载器,在calsspath根目录下加载指定的资源文件。
//InputStreamips =ReflectTest2.class.getResourceAsStream("resources/config.properties");//直接用字节码文件对象调用getResourceAsStream方法来获取文件路径,内部使用的也是类加载器
InputStreamips =ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties");//calsspath目录下的全路径
Propertiesprops = new Properties();
props.load(ips);
ips.close();
StringclassName = props.getProperty("className");
Collectioncollections = (Collection)Class.forName(className).newInstance();
//Collectioncollections = new HashSet();
ReflectPointpt1 = new ReflectPoint(3,3);
ReflectPointpt2 = new ReflectPoint(5,5);
ReflectPointpt3 = new ReflectPoint(3,3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
//pt1.y= 7;
//collections.remove(pt1);
System.out.println(collections.size());
}
}