------- android培训、java培训、期待与您交流! ----------
反射
反射的基石--->Class类
Java程序中的各个java类属于同一类食物,描述这类事物的java类名就是Class。
每一个java类应该都有类名、父类、接口、方法、字段、构造方法等等特点,
所以Class这个类就是对这些共同特点进行抽象化的结果。
----------
!!!!过程:先从硬盘上的class文件加载到内存中来,
再用字节码去复制对象,
所以可以通过对象来获取该对象所属的类的字节码:new Date().getClass();
!!!!!
----------
每一个class文件即为Class类的实例对象。
Class.forName("java.lang.String");得到String类的字节码,
个人理解抽象体系:
Class类
类1
对象1
对象2
类2
对象1
对象2
透彻分析反射的基础
明白class类的含义:它用于描述一类事物的共性,该类事物有什么属性、没有什么属性,至于属性的值则由这个类的实例对象来确定,不同实例对象有不同的属性值。
Class cls1 = 字节码1;
Class cls2 = 字节码2;
1.获取类的字节码的方式?System.class、new Date().getClass()、Class.forName("类的全限定名");
2.一个类的字节码的是唯一的,而同一个类可以有多个对象!
Class cls1 = str1.getClass();
Class cls2 = String.class();
Class cls3 = Class.forName("java.lang.String");//可以接收一个变量,等运行时再指定!读配置文件!
cls1 == cls2 == cls3;//true
int.class == Integer.class;//false
Integer.TYPE == int.class;//true,
注意:基本数据类型包装类中都有一个TYPE属性来代表其基本数据类型的class对象
int[].class.isPrimitive();//不是原始类型,因为它是数组。
总之只要是源程序中出现的类型,都有各自的Class实例对象,包括int[],void...
反射就是把Java类中的各种成分映射成相应的java类。
3.9个预定义的Class实例对象
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
3.反射的应用
Constructor类
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
//获取一个参数类型为StringBuffer的构造方法。通过指定参数类型和个数来指定需要获取的构造方法!!!
注意:编译时只进行语法检查,等式右边没有赋给左边。
所以编译器只知道constructor1是一个构造器对象,而不知道它具体是那个类的构造器!再通过该构造器创建对象时,必须进行强制转换!
//获取构造器类对象,编译时只检查,而没有执行=号的语法。
故不明确到底是那个类的构造器,运行时创建对象应传递与它的参数一致,而且将新创建的对象强转成实际的类类型。
String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));
//编译时只知道是个构造方法,而不知道使用该构造方法创建的对象到底是哪个类的对像!
创建对象的过程:class--->Contructor类对象---> Object newInstance();
总结:1.在通过class对象获取构造器,需传递指定class类型的参数来指定构造方法是哪一个;例子见以上。
2.通过该构造器出创建对象时,必须进行强制转换。原因见以上!
3.创建对象时,必须传递与获取构造器时传递的参数类型一致的实际参数!
4.也可以调用Class类中的getInstance()获取类中的空参数的构造方法。它提供了简便的方法。
Filed类
代表类中的一个成员变量
public class ReflectPoint{
private int x;
public int y;
}
ReflectPoint pt1 = new ReflectPoint(3,5);
Field fieldY = pt1.getClass().getField("y");
fieldY的值是多少?注意:它代表字节码中的字段而没有对应到对象上,不同对象的y值可以是不一样的,所以它不是5!
它不代表具体的值,所以要用以下方法获取,该方法需要指明是哪一个对象。
fieldY不是对象上的变量,而是类上某个对象上对应的值。所有要获取对象上的成员,必须接受对象参数!
fieldY.get(pt1);//pt1对象的该字段的值
注意:获取到对象上的私有成员之前需要先 fieldY.setAccesible(true);
Filed变量反射的综合案例!!!
需求:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”。
public class ReflectPoint{
private int x;
prinvate int y;
public String str1 = "abc";
public String str2 = "basketball";
public String str3 = "itcast";
}
main{
changeStringValue(pt1);
}
private static void changeStringValue(Object obj)throws Exception{
Field[] fields = obj.getClass().getFields();
for(Field field : fields){
//if(field.getType().equals(String.class)){ 注意String类型的字节码只有一份,直接拿==号来比!equals也可以,但语义不准确!
if(field.getType() == String.class){ //是否是同一个字节码
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b','a');
field.set(obj,newValue);
}
}
}
---------------------------------------------------------------------------------------------
Method类----------------->注意打包与拆包的问题!!
代表某个类中的一个成员方法
注意:方法与对象无关,应该先通过字节码获取该方法,然后再通过对象去调用该方法。
需求:调用str1.charAt(1);
Method methodCharAt = String.class.getMethod("charAt",int.class);
//第一个参数指定要获取的String中的方法名,第二个参数指明该方法的参数类型,
因为存在方法重载的情况,故必须指明该方法接受的参数。
即获取String类中名称为charAt,参数类型为int的方法。
methodCharAt.invoke(str1,1);//调用方法!第一个参数接收对象,调用该对象中的带有第二个参数的方法。方法启动!
methodCharAt.invoke(null,1);//静态方法调用,不需要对象。
注意:
methodCharAt.invoke(str1,new Object[]{2});//JDK1.4 的invoke!
//使用Object[]数组来表示要获取方法中的参数的个数以及具体值(参数个数是1,类型为int)。
methodCharAt.invoke(Object obj,Object... args);//JDK1.5 的invoke!
可以传递任意个数的对象参数,注意第二个参数是对象!整数有自动装箱的功能,比较特殊。
划圆的例子:将划园的方法定义在圆这个类中,
因为该方法要访问圆的私有属性(圆心和半径),
如果定义在其他类中,那么其他类就要访问圆的私有属性,不合适。
应用:用反射的方式执行某个类中的main方法
为什么要用反射的方式来调用?
我们不知道类的名字。
main{
String startingClassName = args[0];//将该类的名字告诉测试类的主函数,执行这个类,需要手动的将目标的全限定名赋值给args[0]!
Method mainMethod = Class.forName(startingClassName).getMethod("main",String[].class);
//mainMethod.invoke(null,new String[]{"11","22","33"} );//调用目标类中的静态的main方法,------->error
JDK1.5为了兼容1.4的invoke,会将传递进来的第二个参数进行拆包,拆包后的每一个元素分别做为一个元素。
相等于3个字符串元素的参数。而我们main方法只要接收一个String类型的数组对象,故我们可以继续打一次包!
mainMethod.invoke(null,new Object[] {new String[] {"11","22","33"} });
//再用new Object[]{}来打一次包,这样当java拆掉包之后,就只有一个对象参数了!!!!!!!!!!
或者:
methodCharAt.invoke(str1,(Object)new String[]{"abc","123,","233"});//相等于告诉java当成一个Object,别拆包!
}
Method methodCharAt = String.class.getMethod("charAt",String.class);//参数为一个字符串类型的!
methodCharAt.invoke(str1,new String[]{"abc","123,","233"});//这个会报错,因为java会将字符串值拆包,看成是3个字符串参数!这样就与获取类中的方法接受的参数的个数不一致了!应该按照以下方法:
methodCharAt.invoke(str1,new Object[]{new String[]{"abc","123,","233"}});//java认为只有一个参数,因为Object[]{}的长度为1,如果是长度为2,则在Object数组中new两个String数组!!new Object[]{new String[]{"abc","123,","233"},new String[]{"abc","123,","233"}}故必须用Object[]{}来打包!java会拆包!
也可以这样:
methodCharAt.invoke(str1,(Object)new String[]{"abc","123,","233"});//相等于告诉java当成Object。
----------------------------------------------------------------------------------------------
数组的反射!
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[3];
a1.getClass() == a2.getClass();//true
a1.getClass() == a3.getClass();//false
a1.getClass() == a4.getClass();//false
System.out.println(a1.getClass().getName());
System.out.println(a1.getSuperclass().getNam()); //java.lang.Object
System.out.println(a4.getClass().getSuperclass().getName()); //java.lang.Object
Object aObj1 = a1;可以
Object bObj2 = a4;可以
Object[] aObj3 = a1;不可以,因为a1是int类型的数组,里面装的是int,不可以将其转成Object类型!
Object[] aObj4 = a3;可以
Object[] aObj5 = a4;可以
System.out.println(a1);
System.out.println(a4);
为了打印数组中的元素,可使用Arrays中的asList()的方法把数组转成List的集合:
System.out.println(Arrays.asList(a1));//[[I @1...]整数的数组打印成这样了
System.out.println(Arrays.asList(a4));//[a,b,c]
JDK1.4
public staitc List asList(Object[] a)
//它接收的是Object[],传入字符串数组,它吧元素取出让后放入集合打印!
当传入的是int类型数组时,不符合它的参数要求,它就交给JDK1.5来处理
JDK1.5
public staitc List asList(T...t)//它把该int类型的数组当成是一个Object对象来看待了,然后把它的地址值放入集合中打印了
数组反射应用
需要应用Array类:java.lang.reflect.Array
参考:ReflectTest.java
//使用反射打印数组中的每一个元素------->万能的打印方法!!!!!!牛!!!!!!
private static void printObject(Object obj){
Class clazz = obj.getClass();
if(clazz.isArray()){
int length = Array.getLength(obj);
for(int i=0;i<length;i++){
System.out.println(Array.get(obj,i));
}
}else{
System.out.println(obj);
}
}
思考:怎么得到数值中的元素的类型?
Object[] obj = new Object[]{"abc",12};
只能获取指定obj[0].getClass().getName();的类型!而不能得到该obj数组的类型,