张孝祥java高新技术笔记:
- 理解反射的基础Class类
- 反射的概念
- 构造方法的反射
- 成员变量的反射
- 成员方法的反射
- 对接收数组参数的成员方法进行反射
- 数组与Object的关系及数组的反射类型
- 数组的反射应用
- 框架的概念及用反射技术开发框架的原理
不是1.5的新特性,从1.2就有了。
Java类用于描述一类事物的共性,该类事物有什么属性,至于这个类的属性值是什么,则是由这个类的对象来确定,不同的实例对象有不同的属性值。java程序中的各个类,他们也属于同一类事物,也可以用一个类来描述这类事物,这个类就是Class,要注意和小写的class关键字的区别。Class类描述那些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表,等等,学习反射,首先要明白这个Class类。
对比提问1:众多的人用一个什么类表示?众多的java类用什么表示?
人-》Person
Java类-》Class 此时很明显的体现了java面向对象的思想(一切皆为对象)。
对比提问2:Person类代表人,他的实例对象就是张三,李四这样一个个具体的人,Class类代表java类,那么他的实例对象又分别对应什么呢?
对应的是各个类在内存中的字节码(一个类有且仅有一份),例如,Person类的字节码,ArrayList类的字节码等等。(字节码就是类被加载到jvm中内存中的Class类的实例,然后利用这个字节码复制一个个指定类的对象)。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里边的内容就是类的字节码,不同的类的字节码是不同的,所以他在内存中的内容是不同的,这一个个的空间分别用一个个对象来表示,这些对象显示具有相同的类型,这个类型是什么呢?就是 Class类。
三种获取Class对象的方法:
不能用Class c = new Class(),没有这个构造方法的,应该采取下边的三种方法:
1). Person.class 类名.class的方式
2). Person p = new Person; p.getClass() person类的实例getClass的方式
3). Class.forName(“类名(全路径)”); 这个如果在jvm内存中没有找到指定类的字节码,就先将指定类加载到jvm内存中,然后在返回指定的类的Class实例,如果jvm中存在就直接返回。
需要注意的是:一般实现反射的话都是使用第三种方式Class.forName(“”).因为这个参数可以写成变量传进来,此时就可以读取配置文件的字符串来动态的获取相关类的字节码实例。
1 /** 2 * 获取一个类的字节码对象的三种方法 同一个类,类实例(字节码对象)在jvm中有且只有一份 3 * 4 * @throws ClassNotFoundException 5 */ 6 @Test 7 public void test1() throws ClassNotFoundException { 8 String str = "abc"; 9 // 表示返回此对象运行时类的 Class对象 10 Class<? extends String> cls1 = str.getClass(); 11 12 // 编译器通过String就可以确定返回String类型的Class对象。 13 Class<String> cls2 = String.class; 14 15 // 给编译器看的,编译器并不知道通过forName会返回什么类型的Class对象 16 Class<?> cls3 = Class.forName("java.lang.String"); 17 18 System.out.println(cls1 == cls2); // true 19 System.out.println(cls1 == cls3); // true 20 21 }
由代码18和19行知,每个类不管使用三种方式中的哪种方式来获取Class对象都是一份,从而证明了内存中同一个类的Class对象有且只有一份。
9个预定义的class对象:
八种基本数据类型和void关键字。
1 /* 2 * 预定义的Class对象 3 */ 4 @Test 5 public void test2() { 6 Class<String> cls2 = String.class; 7 // primitive是原始的意思//判定指定的 Class对象是否表示一个基本类型(原始类型) 8 System.out.println(cls2.isPrimitive()); 9 10 // 判定指定的 Class 对象是否表示一个基本类型 11 System.out.println(int.class.isPrimitive()); 12 13 // 基本类型和对应包装类Class对象不是同一个 14 System.out.println(int.class == Integer.class); //false int和Integer包装类不是同一份字节码 15 16 /* 17 * Integer.TYPE代表的是integer包装的基本类的字节码(使用TYPE是为了更好的表示), 18 * 根据文档:表示基本类型 int 的 Class 实例 19 */ 20 System.out.println(int.class == Integer.TYPE); //true 21 22 //数组有对应的数组类(Array类) 23 System.out.println(int[].class.isPrimitive()); //false 24 25 // 是不是数组类class对象 26 System.out.println(int[].class.isArray()); 27 }
反射在很多地方都能用到,比如Spring,struts,junit等一些框架中,由此可以看出反射的重要性。
反射就是把java类中的各个成分映射相应的java类。例如:一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一些方法来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,他们是Filed,Method,Constructor,Package等等。(可以查看Class类的api就可以了解相关的方法)。
一个类的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这才是要点。
Constructor类代表某个类中的一个构造方法。
1 /** 2 * 构造方法的反射 3 * @throws NoSuchMethodException 4 * @throws SecurityException 5 * @throws InstantiationException 6 * @throws IllegalAccessException 7 * @throws IllegalArgumentException 8 * @throws InvocationTargetException 9 */ 10 @Test 11 public void test3() throws NoSuchMethodException, SecurityException, 12 InstantiationException, IllegalAccessException, 13 IllegalArgumentException, InvocationTargetException { 14 Class<String> clazz = String.class; 15 //获取String类的所有构造方法 16 Constructor<?>[] constructors = clazz.getConstructors(); 17 18 /* 19 * 通过构造函数反射实例化对象 20 */ 21 String str1 = new String(new StringBuffer("adc")); 22 Constructor<String> constructor1 = String.class 23 .getConstructor(StringBuffer.class); 24 String str2 = constructor1.newInstance(new StringBuffer("abc")); 25 26 //使用默认的构造方法实例化对象,newInstance 27 String str3 = String.class.newInstance(); 28 }
使用newInstance方法来实例化对象的情况,newInstance方法先得到相应类的默认的构造方法,然后使用该默认的构造方法来实例化对象。通过查看源码知:java会用 缓存机制来缓存默认的构造方法实例,由此可以看出用了缓存说明获取Constructor对象是一个很耗时的操作。
Filed类代表某个类中的一个成员变量,即某个类的字节码对象的字段对象,不和具体的类的实例对应。
1 package mytest; 2 3 import java.util.Date; 4 5 public class ReflectPoint { 6 private Date birthday = new Date(); 7 8 private int x; 9 public int y; 10 public String str1 = "ball"; 11 public String str2 = "basketball"; 12 public String str3 = "itcast"; 13 14 public ReflectPoint(int x, int y) { 15 super(); 16 this.x = x; 17 this.y = y; 18 } 19 20 public ReflectPoint(String str1, String str2, String str3) { 21 super(); 22 this.str1 = str1; 23 this.str2 = str2; 24 this.str3 = str3; 25 } 26 27 28 29 @Override 30 public int hashCode() { 31 final int prime = 31; 32 int result = 1; 33 result = prime * result + x; 34 result = prime * result + y; 35 return result; 36 } 37 38 @Override 39 public boolean equals(Object obj) { 40 if (this == obj) 41 return true; 42 if (obj == null) 43 return false; 44 if (getClass() != obj.getClass()) 45 return false; 46 final ReflectPoint other = (ReflectPoint) obj; 47 if (x != other.x) 48 return false; 49 if (y != other.y) 50 return false; 51 return true; 52 } 53 54 @Override 55 public String toString() { 56 return str1 + ":" + str2 + ":" + str3; 57 } 58 59 public int getX() { 60 return x; 61 } 62 63 public void setX(int x) { 64 this.x = x; 65 } 66 67 public int getY() { 68 return y; 69 } 70 71 public void setY(int y) { 72 this.y = y; 73 } 74 75 public Date getBirthday() { 76 return birthday; 77 } 78 79 public void setBirthday(Date birthday) { 80 this.birthday = birthday; 81 } 82 }
1 /** 2 * 成员变量的反射 3 * @throws SecurityException 4 * @throws NoSuchFieldException 5 * @throws IllegalAccessException 6 * @throws IllegalArgumentException 7 */ 8 @Test 9 public void test4() throws NoSuchFieldException, SecurityException, 10 IllegalArgumentException, IllegalAccessException { 11 ReflectPoint pt1 = new ReflectPoint(3, 5); 12 Class<? extends ReflectPoint> clazz = pt1.getClass(); 13 /* 14 * 获取指定的字段对象 15 * fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值 16 */ 17 Field fieldY = clazz.getField("y"); 18 19 //获取pt1对象上的y字段的值 20 System.out.println("变量Y的值:" + fieldY.getInt(pt1)); 21 22 /* 23 * getField只能获取public和defalut修饰的字段,此时x是私有变量。 24 */ 25 /* 26 * Field fieldX = clazz.getField("x"); 27 * System.out.println(fieldX.getInt(pt1)); 28 */ 29 30 /* 31 * getDeclaredFields可以获取类中所有的成员变量 包括私有的变量 32 */ 33 Field[] fields = clazz.getDeclaredFields(); 34 System.out.println("成员变量个数:" + fields.length); 35 36 /* 37 * 获取私有变量x的值 38 * 虽然获取到x了,但是没有权限访问其中的值,所以需要设置x字段对象为可以访问的。 39 */ 40 Field fieldX = clazz.getDeclaredField("x"); 41 fieldX.setAccessible(true); 42 System.out.println("变量X的值:" + fieldX.getInt(pt1)); 43 }
成员变量反射的综合案例:
1 /** 2 * 成员变量反射的综合案例 将任意一个对象中的所有String类型的成员变量所对应的字段内容中的“b”改成”a” 3 * @throws SecurityException 4 * @throws NoSuchFieldException 5 * @throws IllegalAccessException 6 * @throws IllegalArgumentException 7 */ 8 @Test 9 public void test5() throws NoSuchFieldException, SecurityException, 10 IllegalArgumentException, IllegalAccessException { 11 ReflectPoint pt1 = new ReflectPoint("ball", "basketball", "itcast"); 12 System.out.println("修改前:"); 13 System.out.println(pt1.str1); 14 System.out.println(pt1.str2); 15 System.out.println(pt1.str3); 16 17 Class<? extends ReflectPoint> clazz = pt1.getClass(); 18 // /Field fieldX = clazz.getDeclaredField("x"); 19 Field[] fields = clazz.getDeclaredFields(); //获取所有的字段 包括私有字段 20 for (Field field : fields) { 21 /* 22 * 获取字段的类型(返回的是类型的字节码实例) 判断是否和String类的字节码相等就能判断是否是String类型的字段了。 23 * 因为字节码只有一个,所以使用 == 比使用equeals更能体现意义 24 */ 25 Class<?> filedType = field.getType(); 26 if (filedType == String.class) { 27 field.setAccessible(true); 28 String oldValue = (String) field.get(pt1); 29 String newValue = oldValue.replace("b", "a"); 30 field.set(pt1, newValue); //重新将替换后的新值set到相关到对象中去。 31 } 32 } 33 34 System.out.println("修改后:"); 35 System.out.println(pt1.str1); 36 System.out.println(pt1.str2); 37 System.out.println(pt1.str3); 38 39 }
结论:
从上可以看出对于一些对象,可以使用反射将其中的值都改掉,spring实例化里边的值和初始化对象的值都是如此实现的。
Method类代表某个类中的一个成员方法。
1 /** 2 * 成员方法的反射 3 * @throws SecurityException 4 * @throws NoSuchMethodException 5 * @throws InvocationTargetException 6 * @throws IllegalArgumentException 7 * @throws IllegalAccessException 8 */ 9 @Test 10 public void test6() throws NoSuchMethodException, SecurityException, 11 IllegalAccessException, IllegalArgumentException, 12 InvocationTargetException { 13 String str = "abc"; 14 //获取指定方法的Method类的实例 15 Method method = String.class.getMethod("charAt", int.class); 16 //调用指定实例的方法,并传参
17 //如果是Method代表的是静态方法,那么第一参数为null
18 System.out.println(method.invoke(str, 1)); 19 }
问题:
用反射的方式执行某个类中的main方法,写一个程序,根据用户提供的类名,去执行该类的main方法。如果使用传统的方式执行main方法可以直接Test.main(new String[]{}); 但是不能动态指定类名(用户提供的),因为传统的方式都已经编译了不能再改了。
1 /** 2 * 对接收数组参数的成员方法进行反射 用反射的方式执行某个类中的main方法,写一个程序,根据用户提供的类名,去执行该类的main方法。 3 * 4 * @throws ClassNotFoundException 5 * @throws SecurityException 6 * @throws NoSuchMethodException 7 * @throws InvocationTargetException 8 * @throws IllegalArgumentException 9 * @throws IllegalAccessException 10 */ 11 @Test 12 public void test7() throws ClassNotFoundException, NoSuchMethodException, 13 SecurityException, IllegalAccessException, 14 IllegalArgumentException, InvocationTargetException { 15 16 String mainStr = "mytest.TestArguments"; 17 Method mainMethod = Class.forName(mainStr).getMethod("main", 18 String[].class); 19 mainMethod.invoke(null, new String[] { "a", "b", "c" }); 34 }
1 /** 2 * 测试反射main方法的类 3 */ 4 class TestArguments { 5 public static void main(String[] args) { 6 for (String arg : args) { 7 System.out.println(arg); 8 } 9 } 10 }
上述代码报错:java.lang.IllegalArgumentException: wrong number of arguments
明显是参数个数错误,main方法确实需要一个参数,而我也传了一个参数,为什么会错呢?
因为在为invoke传递数组类型的参数的时候,按照jdk1.5的做法,整个数组是一个参数,而按照jdk1.4的语法,会将数组拆开,每个元素对应一个参数,所以当把一个字符串数组作为一个参数传递给invoke方法时,javac会按照哪种方法进行处理呢?jdk1.5肯定要兼容jdk1.4的做法,会先按照1.4的语法处理,即把数组拆开为每一个元素作为一个参数。所以如果按照上边的方式会出现异常。可以采用下边的方式:
1 /** 2 * 对接收数组参数的成员方法进行反射 用反射的方式执行某个类中的main方法,写一个程序,根据用户提供的类名,去执行该类的main方法。 3 * @throws ClassNotFoundException 4 * @throws SecurityException 5 * @throws NoSuchMethodException 6 * @throws InvocationTargetException 7 * @throws IllegalArgumentException 8 * @throws IllegalAccessException 9 */ 10 @Test 11 public void test7() throws ClassNotFoundException, NoSuchMethodException, 12 SecurityException, IllegalAccessException, 13 IllegalArgumentException, InvocationTargetException { 14 15 String mainStr2 = "mytest.TestArguments"; 16 Method mainMethod2 = Class.forName(mainStr2).getMethod("main", 17 String[].class); 18 19 /* 20 * jdk1.4的做法 21 * 重新包装字符串数组为一个object数组,此时按照1.4的方式拆开之后只有一个参数就是里边的string数组 22 */ 23 mainMethod2 24 .invoke(null, new Object[] { new String[] { "a", "b", "c" } }); 25 26 /* 27 * jdk1.5的做法 28 * 重新包装字符串数组为一个object,此时给invoke方法传递的参数不是数组,编译器不会去拆解了, 29 * (数组也是Object) 30 */ 31 mainMethod2.invoke(null, (Object) new String[] { "a", "b", "c" }); 32 33 }
根据文档中class的解释,对于数组的Class对象实例,具有相同维数(一维数组二维数组等)和元素类型的属于同一个类型,即具有相同的Class实例对象。中文文档的说明:所有具有相同元素类型和维数的数组都共享该 Class
对象。
int[] a1 = new int[] { 1, 2, 3 }; Integer[] a11 = new Integer[] { 1, 2, 3 }; int[] a2 = new int[4]; int[][] a3 = new int[2][3]; String[] a4 = new String[] { "a", "b", "c" }; // true System.out.println(a1.getClass() == a2.getClass()); // 都是一维数组,但是数组元素类型不一致,直接编译不通过。 // System.out.println(a1.getClass() == a4.getClass()); // 数组元素类型以致,但是维数不同,直接编译不通过。 // System.out.println(a1.getClass() == a3.getClass());
数组的反射类型的name:
/* * 基本类型的数组getName */ //[I System.out.println("int[]:" + a1.getClass().getName()); //int System.out.println("int:" + int.class.getName()); /* * 引用类型的数组getName */ // [Ljava.lang.String System.out.println("String[]:" + a4.getClass().getName()); // java.lang.String System.out.println("String:" + String.class.getName()); /* * 与getName相对应的是forName * 根据数组类型的Name类获取Class */ // 基本类型 Class<?> c1 = Class.forName("[I"); System.out.println(c1.isArray()); // true // 引用类型 Class<?> c2 = Class.forName("Ljava.lang.String;"); System.out.println(c2.isArray()); // true
对于"[I"中,[表示数组,I表示数组元素类型是int。
具体I代表什么参考getName方法的api注释:
基础类型: Element Type Encoding boolean Z byte B char C double D float F int I long J short S 引用类型: Element Type Encoding class or interface Lclassname; 例子: String.class.getName() returns "java.lang.String" byte.class.getName() returns "byte" (new Object[3]).getClass().getName() returns "[Ljava.lang.Object;" (new int[3][4][5][6][7][8][9]).getClass().getName() returns "[[[[[[[I"
getSimpleName方法
// getSimpleName // String System.out.println(String.class.getSimpleName()); // int[] System.out.println(a1.getClass().getSimpleName());
说明Object是数组的父类。
// 说明Object是数组的父类 System.out.println(a1.getClass().getSuperclass().getName()); // java.lang.Object System.out.println(a4.getClass().getSuperclass().getName()); // java.lang.Object
令:
基本类型的一维数组可以当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当Object使用又可以当Object[]类型使用。
打印数组:
// 打印数组 System.out.println(a4); // 不等同于下边,这个为什么呢? System.out.println(a4.getClass().getName() + "@" + a4.hashCode());
Arrays.asList在处理int[]和String[]的差异。
System.out.println(Arrays.asList(a1)); // [[I@1b9a538] System.out.println(Arrays.asList(a11)); // [1,2,3] System.out.println(Arrays.asList(a4)); // [a, b, c]
从打印结果可以看出,对于int[],打印出来的是int[]数组这个对象,转换成的list只有一个元素就是这个int[],所以打印出来的是这个int[]的地址,而对于String[],成功转换成list,并且数组中每个元素是一个list元素,从而打印出[a,b,c],这样的原因是什么呢?
原因就是:为了兼容jdk1.4,在使用的时候先使用jdk1.4的语法,因为1.4没有可变参数,而1.5有可变参数了,所以对于1.4传入数组而1.5需要传入可变参数的方法尤其是要注意,当符合这种情况的方法的参数传入的是数组对象的时候,就有可能会出现问题。
asList方法在1.4版本是要求传入Object[]的,而1.5是可变参数的(不管什么类型,每个对象都作为一个参数),所以在传入int[],在1.4的情况下是是不符合传入Object[] 的asList方法的,所以使用1.5的语法,此时int[]被当作一个元素,转换成list之后,list只有一个元素就是int[]对象,打印出来就是int[]对象的地址,但是传入String[]情况就不同了,先检查1.4的语法,String[]是符合传入Object[]的asList的,此时传入的String[]会被当作多个元素遍历了而不是当作一个String[]对象,此时就转换成的list就是多个元素了,就发生了上述的问题。
需要了解的是:1.4的可变参数是以数组的方式实现的,而1.5是Type … args方式实现的。
Array类(Array工具类)完成对数组的反射,是代表了对于数组类的描述。文档中说明:Array
类提供了动态创建和访问 Java 数组的方法。
和Arrays类的区别:Array是数组类的反射类,是对数组的一个描述,而Arrays是操作数组的一个工具类。
1 /** 2 * 数组的反射应用 3 */ 4 @Test 5 public void test9() { 6 // Array和Arrays的区别 7 printObject(new int[] { 1, 2, 3 }); 8 9 } 10 11 // Array 数组类对应的反射类 12 public static void printObject(Object obj) { 13 Class<?> clazz = obj.getClass(); 14 if (clazz.isArray()) { // 判断是不是Array类 15 int len = Array.getLength(obj); //获取具体指定数组对象的长度 16 for (int i = 0; i < len; i++) { 17 System.out.println(Array.get(obj, i)); 18 } 19 } else { 20 System.out.println(obj); 21 } 22 }
待补充。