JAVA提高三:反射总结
为前期学习过反射,再这里再次复习总结下:【转载请说明来源:http://www.cnblogs.com/pony1223/p/7659210.html 】
一、透彻分析反射的基础_Class类
Class是一个类,他代表一类事物,它代表一类什么样的事物呢? Java 程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class.
思考:
众多的人可以用一个什么类表示? Person
众多的类用一个什么类来表示? 答案是Class
我们知道Person代表一个人,一个人有他的身高,体重等属性,有叫,喊等方法,人还有他的父类,它的构造方法.接口等.
那么Class类呢?他是所有类的类.那么他可以干什么呢?他可以获取某一个类的属性,方法,父类,构造方法,接口等....
(1)Class的本质是什么?
我们现在定义两个对象:
Person p1 = new Person();
Person p2 = new Person();
那如何获取这两个对象的Class类呢?用Class cls1 = new Class()可以么?不可以,因为Class跟们就没有这个构造方法.
Class类在内存中代表的就是一段字节码,那什么是字节码呢?当我们在源程序用到Person这个类的时候,首先把这个类编译成class文件以后,存放到硬盘上,这些class文件其实就是一堆二进制代码,要把这一堆二进制代码加载到内存中来,然后才可以去创建一个个对象.当我在程序中用好多java类,有Person类,Math类,Date类,我又到这三个类,那么在内容中会有几个字节码呢?有三份字节码,每一份字节码就是Class的一个实例对象.
Class cls1 = Date.class;//代表Date类的字节码
Class cls2 = Pserson.class;//代表Person类的字节码
(2)那如何来得到这个Class呢?
总结:一个类的Class字节码只存在一份.
Class.isPrimitive()方法.判断一个类型是否是基本的数据类型.
总结:只要在源程序中出现的类型,都有各自的Class实例对象,例如:int[], void...
二、理解反射的概念
第一种官方理解:
反射是指在运行状态中,对于任意一个类,都可以获取到这个类的所有属性和方法;对于任意一个对象,都能够调用这个对象的任意方法和属性;这种动态获取信息及动态调用对象的方法,称为JAVA语言的反射机制。
第二种个人理解:
反射,就是把Java类中的各个成分映射成相应的Java类.我们想想,一个类身上有什么,有包,有方法,有属性,有构造方法,有接口.而这些方法相应的又返回一个类.看下面这个api,通过Class的方法可以得到什么.
三、构造方法的反射应用
package study.javaenhance; import java.lang.reflect.Constructor; public class ReflectTest { public static void main(String[] args) throws Exception { String str = "abc"; Class clz1 = String.class; Class clz2 = str.getClass(); Class clz3 = Class.forName("java.lang.String"); System.out.println(clz1 == clz2); System.out.println(clz1 == clz3); getConstructor(); } /** * 目前String 类型中有很多构造方法,我想要找到获取构造方法为StringBuffer类型的一个 * @throws Exception * @throws SecurityException */ public static void getConstructor() throws SecurityException, Exception { String.class.getConstructor(StringBuffer.class); //那么上面的获取到构造方法有什么用呢? /** * 通常,我们创建String 对象为 String abc = new String(new StringBuffer("abc")) 方式, * 下面我们通过反射来实现: * 1.加载类 * 2.解析类 * */ Constructor constructor = String.class.getConstructor(StringBuffer.class); String str = (String) constructor.newInstance(new StringBuffer("abc")); System.out.println(str); } }
具体的原理,如何使用可以参考:http://www.cnblogs.com/pony1223/p/7445950.html
四、成员变量的反射
成员变量的反射,在这里我们先定义一个类,用于讲解成员变量的反射。
package study.javaenhance.util;import java.util.Date; public class ReflectPoint { private Date birthday = new Date(); 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; final 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; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
接下来,我们要采用反射的方式来获取其中的x 成员和 y成员
//成员变量的反射 //1.首先我们先初始化一下ReflectPoint类的xy值,以便反射的时候获取其值 ReflectPoint rp = new ReflectPoint(2,4); //2.要反射,后续加载类 Field xField = rp.getClass().getField("x"); //3.解析类 System.out.println(xField.get(rp));
上述会出现一个错误:
Exception in thread "main" java.lang.NoSuchFieldException: x
at java.lang.Class.getField(Class.java:1520)
at study.javaenhance.ReflectTest.main(ReflectTest.java:25)
也就是说找不到这个,为什么呢?很明显,因为是私有的,因此需要采用另外一个方法来获取,修改如下:
ReflectPoint rp = new ReflectPoint(2,4); //2.要反射,后续加载类 Field xField = rp.getClass().getDeclaredField("x"); //3.解析类 System.out.println(xField.get(rp));
Exception in thread "main" java.lang.IllegalAccessException: Class study.javaenhance.ReflectTest can not access a member of class study.javaenhance.util.ReflectPoint with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Field.doSecurityCheck(Field.java:960)
at java.lang.reflect.Field.getFieldAccessor(Field.java:896)
at java.lang.reflect.Field.get(Field.java:358)
at study.javaenhance.ReflectTest.main(ReflectTest.java:27)
现在是看到,却没法拿到,需要修改访问的权限打开,修改为如下:
ReflectPoint rp = new ReflectPoint(2,4); //2.要反射,后续加载类 Field xField = rp.getClass().getDeclaredField("x"); xField.setAccessible(true); //3.解析类 System.out.println(xField.get(rp));
对于非私有的成员变量,没有这么麻烦,如下即可:
//成员变量的反射 //1.首先我们先初始化一下ReflectPoint类的xy值,以便反射的时候获取其值 ReflectPoint rp = new ReflectPoint(2,4); //2.要反射,后续加载类 Field xField = rp.getClass().getDeclaredField("x"); xField.setAccessible(true); //3.解析类 System.out.println(xField.get(rp)); Field yField = ReflectPoint.class.getField("y"); System.out.println(yField.get(rp));
下面做一个小例子,我们看到ReflectPoint类中有很多成员变量,我们现在要做的是将String类型的成员变量的值中含b的字母替换称为a,如:ball 替换为aall
代码如下:
private static void changeStringValue(Object rp) throws Exception { //思路:1.反射获取要所有的成员变量 2.获取成员变量的类型判断是否为String类型 3.如果是则获取其值 4.替换 5.回填 Field[] fields = rp.getClass().getFields(); for (Field field : fields) { if(field.getType() == String.class) { String oldValue = (String) field.get(rp); String newValue = oldValue.replace("b", "a"); field.set(rp, newValue); } } }
客户端调用代码:
ReflectPoint rp = new ReflectPoint(2,4); //2.要反射,后续加载类 Field xField = rp.getClass().getDeclaredField("x"); xField.setAccessible(true); //3.解析类 System.out.println(xField.get(rp)); Field yField = ReflectPoint.class.getField("y"); System.out.println(yField.get(rp)); //案例:changeStringValue System.out.println(rp); changeStringValue(rp); System.out.println(rp);
结果为:
2
4
ball:basketball:itcast
aall:aasketaall:itcast
五、成员方法的反射
//方法的反射 Method methodCharAt = String.class.getMethod("charAt", int.class); System.out.println(methodCharAt.invoke(str, 1)); //兼容JDK5.0之前的写法 System.out.println(methodCharAt.invoke(str, new Object[]{2}));
可以看出还是比较简单的和成员的方式差不多,但是下面需要注意的是,如果有一个类,我们需要通过反射的方式去调用main方法,该如何去做呢?
先定义一个类:
class TestArguments{ public static void main(String[] args){ for(String arg : args){ System.out.println(arg); } } }
最先想到的代码如下:
//调用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 这种方式,因为有些场景我们的类是传入的是你运行期才知道的 Class clazz = Class.forName("study.javaenhance.TestArguments"); Method method = clazz.getMethod("main",String[].class); //因为main方法是静态方法,所以第一个参数传入的为null method.invoke(null,new String[]{"1","2","3"});
结果如下:
为什么会这样呢?我们本身的使用其实是正确的,只是因为SUN公司的JDK5.0的可变参数出现后,为了兼容以前的数组方式所以出现了上面的错误。
在JDK5.0前:public Object invoke(Object obj, Object... args) 是采用的为 public Object invoke(Object obj, Object[] obj)
采用的数组方式进行的接受,那么这个时候你传入的是String[]{"1","2","3"} 那么它正好和Object[] obj匹配,因为在1.5前就会开始拆分,那么就变成了传入了3个String类的参数到了main方法上面,因此这个时候就参数类型个数无法匹配了,所以就出现了上面的错误了,如何修改匹配呢?方式如下:
//调用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 这种方式,因为有些场景我们的类是传入的是你运行期才知道的 Class clazz = Class.forName("study.javaenhance.TestArguments"); Method method = clazz.getMethod("main",String[].class); //因为main方法是静态方法,所以第一个参数传入的为null //method.invoke(null,new String[]{"1","2","3"}); //这样拆开后就是main方法想要的参数了 method.invoke(null,new Object[]{new String[]{"1","2","3"}}); //这样就是一个对象传入了,不需要拆了 method.invoke(null,(Object)new String[]{"1","2","3"});
六、数组与Object的关系
先看下面的例子:
//数组与Object的关系 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 System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
从上面的例子说明:
具有相同的维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
代表数组的Class实例对象的个头Superclass方法返回的父类为Object类对应的class
基本类型的一维数组可以被当做Object类型使用个,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类使用,又可以当做Object[]类型使用 --- 这句话需要理解一下,参考下面的代码:
//数组与Object的关系 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 System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object Object aObj1 = a1; //因为a1 是一个int[] 类型的数组所以是object类型 Object aObj2 = a4;//因为a4 是一个String[]类型的数组所以是Object类型 //Object[] aObj3 = a1;// 报错,因为a1 是一个int[]数组里面的元素都是int型,不是Object类型所以不可以如果改成包装类就可以 Object[] aObj4 = a3; //a3 是一个二维数组,那么里面的一维数组就是一个Object类型,所以可以匹配Object[]代表里面每个元素类型为Object Object[] aObj5 = a4; //String[] 里面的每一个元素都是String类型,也是Object类型 所以可以匹配. System.out.println(a1); System.out.println(a4);
我们发现最后a1 和 a4 的输出是:
[I@87816d
[Ljava.lang.String;@422ede
这个是什么东西呢?我们找下JDK文档看下:
在Class 的getName 方法中说明如下:
即代表的是int类型的数组和String类型的数组,但我们肯定不希望输出这个样子,我们希望的看到的是里面的值,那么肯定有人会说用遍历的方法遍历出来,当然这是一种方法,但没有简单的方法吗?
七、数组的反射应用
上面提到了遗留的问题,我们本节来解决,解决前我们先学习两个类:Array类 和Arrays类
Array
类提供了动态创建和访问 Java 数组的方法。需要说明下Array 是 数组的反射类,处于反射包下面,所以我们看到引用类型下对于类,我们是用Class 对于数组我们是用Array
Arrays 此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。
我们现在就是要利用Arrays类中的asList方法来完成显示的目的.
//数组与Object的关系 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 System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object Object aObj1 = a1; //因为a1 是一个int[] 类型的数组所以是object类型 Object aObj2 = a4;//因为a4 是一个String[]类型的数组所以是Object类型 //Object[] aObj3 = a1;// 报错,因为a1 是一个int[]数组里面的元素都是int型,不是Object类型所以不可以如果改成包装类就可以 Object[] aObj4 = a3; //a3 是一个二维数组,那么里面的一维数组就是一个Object类型,所以可以匹配Object[]代表里面每个元素类型为Object Object[] aObj5 = a4; //String[] 里面的每一个元素都是String类型,也是Object类型 所以可以匹配. System.out.println(a1); System.out.println(a4); //Array应用 System.out.println(Arrays.asList(a1)); System.out.println(Arrays.asList(a4));
结果为:
[[I@87816d]
[a, b, c]
为什么呢?如果对前面的数组与Object 关系理解的话,这里就清楚了,因为在5.0之前没有可变参数,因此传入的a1 是int[] 这个时候在Object[] 数组接受的时候发现Object[] = a1 这个是不匹配的,因此这个时候它无法处理,于是会走可变参数可变参数的时候,于是会当做一个参数处理,那么就是List集合里面直接添加这个数组了,然后结果显示为[[I@hashCode 了;而String[] 传入的时候,因为Object[] = String[] 是匹配的于是会走5.0前的方式,于是会拆分,那么List集合中就添加了3个元素,所以就显示出了上述的结果。
那么现在有一个需求:
写一个方法,传递一个参数,判断这个参数是一个数组,还是一个普通类型,如果是普通类型,我就直接把他打印出来。如果是数组,我们就遍历后把值打印出来。(好处,就是不用每次都去for循环,只需要调用这个方法即可,公共性)
private static void printObject(Object obj) { Class clazz = obj.getClass(); if(clazz.isArray()) { //如果是数组,利用数组的反射类Array类来进行相关操作 int len = Array.getLength(obj); for(int i=0;i<len;i++){ System.out.println(Array.get(obj, i)); } } else { System.out.println(obj); } }
即完成了之前的遗留问题.
到此基本的反射应用总结到这里,最后附上全代码。
package study.javaenhance; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import study.javaenhance.util.ReflectPoint; public class ReflectTest { public static void main(String[] args) throws Exception { String str = "abc"; Class clz1 = String.class; Class clz2 = str.getClass(); Class clz3 = Class.forName("java.lang.String"); System.out.println(clz1 == clz2); System.out.println(clz1 == clz3); getConstructor(); //成员变量的反射 //1.首先我们先初始化一下ReflectPoint类的xy值,以便反射的时候获取其值 ReflectPoint rp = new ReflectPoint(2,4); //2.要反射,后续加载类 Field xField = rp.getClass().getDeclaredField("x"); xField.setAccessible(true); //3.解析类 System.out.println(xField.get(rp)); Field yField = ReflectPoint.class.getField("y"); System.out.println(yField.get(rp)); //案例:changeStringValue System.out.println(rp); changeStringValue(rp); System.out.println(rp); //方法的反射 Method methodCharAt = String.class.getMethod("charAt", int.class); System.out.println(methodCharAt.invoke(str, 1)); //兼容JDK5.0之前的写法 System.out.println(methodCharAt.invoke(str, new Object[]{2})); //调用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 这种方式,因为有些场景我们的类是传入的是你运行期才知道的 Class clazz = Class.forName("study.javaenhance.TestArguments"); Method method = clazz.getMethod("main",String[].class); //因为main方法是静态方法,所以第一个参数传入的为null //method.invoke(null,new String[]{"1","2","3"}); //这样拆开后就是main方法想要的参数了 method.invoke(null,new Object[]{new String[]{"1","2","3"}}); //这样就是一个对象传入了,不需要拆了 method.invoke(null,(Object)new String[]{"1","2","3"}); //数组与Object的关系 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 System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object Object aObj1 = a1; //因为a1 是一个int[] 类型的数组所以是object类型 Object aObj2 = a4;//因为a4 是一个String[]类型的数组所以是Object类型 //Object[] aObj3 = a1;// 报错,因为a1 是一个int[]数组里面的元素都是int型,不是Object类型所以不可以如果改成包装类就可以 Object[] aObj4 = a3; //a3 是一个二维数组,那么里面的一维数组就是一个Object类型,所以可以匹配Object[]代表里面每个元素类型为Object Object[] aObj5 = a4; //String[] 里面的每一个元素都是String类型,也是Object类型 所以可以匹配. System.out.println(a1); System.out.println(a4); //Array应用 System.out.println(Arrays.asList(a1)); System.out.println(Arrays.asList(a4)); //写一个方法,传递一个参数,判断这个参数是一个数组,还是一个普通类型,如果是普通类型,我就直接把他打印出来。如果是数组,我们就遍历后把值打印出来 printObject(a1); printObject(a4); printObject("xyz"); } private static void printObject(Object obj) { Class clazz = obj.getClass(); if(clazz.isArray()) { //如果是数组,利用数组的反射类Array类来进行相关操作 int len = Array.getLength(obj); for(int i=0;i<len;i++){ System.out.println(Array.get(obj, i)); } } else { System.out.println(obj); } } private static void changeStringValue(Object rp) throws Exception { //思路:1.反射获取要所有的成员变量 2.获取成员变量的类型判断是否为String类型 3.如果是则获取其值 4.替换 5.回填 Field[] fields = rp.getClass().getFields(); for (Field field : fields) { if(field.getType() == String.class) { String oldValue = (String) field.get(rp); String newValue = oldValue.replace("b", "a"); field.set(rp, newValue); } } } /** * 目前String 类型中有很多构造方法,我想要找到获取构造方法为StringBuffer类型的一个 * @throws Exception * @throws SecurityException */ public static void getConstructor() throws SecurityException, Exception { String.class.getConstructor(StringBuffer.class); //那么上面的获取到构造方法有什么用呢? /** * 通常,我们创建String 对象为 String abc = new String(new StringBuffer("abc")) 方式, * 下面我们通过反射来实现: * 1.加载类 * 2.解析类 * */ Constructor constructor = String.class.getConstructor(StringBuffer.class); String str = (String) constructor.newInstance(new StringBuffer("abc")); System.out.println(str); } } class TestArguments{ public static void main(String[] args){ for(String arg : args){ System.out.println(arg); } } }
参考资料:
张孝祥Java基础增强