------- android培训、java培训、期待与您交流! ----------
反射
• 反射就是把Java类中各种成分映射成相应的Java类:Field Method Constructor Package
▸ Constructor
• 一个Constructor就代表字节码里的一个构造方法 • 得到某个类所有方法 Constructor[] constructors = Class.forName(“java.lang.String”).getConstructors(); • 得到某个方法 Constructor constructor = Class.forName(“jaga.lang.String”).getConstructor(StringBuffer.class); 用参数类型决定获得的是哪个构造方法(可变参数) 在1.5以前用数组传入参数 • 创建对象:用获得的constructor对象的newInstance方法返回一个对象 编译时只知道constructor是一个Constructor对象,运行时才知道对应的是哪一个构造方法 在newInstance方法上传入对应类型的变量,由于constructor的参数类型是StringBuffer,所以要传入的是StringBuffer而不是String 由于newInstance返回的是Object对象,需要进行类型转换 String str = (String)constructor.newInstance(new StringBuffer(“abc”)); • Class类也有一个newInstance方法,该方法对应的是无参构造方法,提供一个便利方法 实际上源代码也是要建立一个Constructor对象并且缓存起来(因为反射比较耗时) 一般步骤:class->constructor->new object
▸ Field
• 代表某个类中的一个成员变量 先建立一个类:ReflectPoint • 先建立一个对象 ReflectPoint • 获得一个成员变量,先获得字节码 getField()参数传入变量名字符串 Field fieldY = pt1.getClass().getField(“y”); 此时fieldY的值不是5,因为没有对应到某个具体对象的成员 • 用get()获取具体的值,参数为对象 fieldY.get(pt1); • 如果成员变量为私有的,用getField方法无法获得 可以使用getDeclaredField()方法获得,但是此时仍然无法访问该成员变量 要使用setAccessible(true)设置,称为暴力反射 Field fieldX = pt1.getClass().getDeclaredField(“x”); fieldX.setAccessible(true); ▸ 练习:将任意一个对象中所有String类型的成员变量对应的字符串内容中的‘b’改成‘a’ private static void changeStringValue(Object obj); • 得到所有字段:Field[] fields = obj.getClass().getFields(); • 遍历所有字段:for (Field field : fields) • 判断字段类型:getType() 不要用if(field.getType().equals(String.class)),同一份字节码的比较用==比 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); circle.draw(); • 获得方法:Mehod methodCharAt = String.class.getMethod(“charAt”, int.class); 由于方法可能有重载,需要确定具体的参数类型 第一个参数是方法名的字符串形式,第二个是表示参数类型的类 • 调用方法:methodCharAt.invoke(str, 1); 第一个参数为要调用方法的对象,第二个是参数的值 如果第一个参数为null说明该方法为静态方法 • 专家模式:谁拥有数据谁就是专家,那么方法就应该分配给他 ▸ 练习:用反射方式执行某个类中的main方法 -- TestArguments类 • 一般调用方法:TestArguments.main(new String[] {“xxx”}); • 为什么要用反射的方式调用:有可能程序不知道要调用的类的名字,通过参数传入,此时无法直接调用该类的方法,需要用反射方式 • String startingClassName = args[0]; Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class); • 在jdk1.5中有自动装箱 在jdk1.4中可变参数用数组存放:new Object[] {参数列表},java会把数组解开获得其中的参数 为了向下兼容,jdk1.5也会把数组拆包 methodCharAt.invoke(str, new Object[] {1}); ▸ 两种传递参数的方法: • mainMethod.invoke(null, new Object[]{new String[] {"1","2","3"}}); java会把String数组解开,当成3个参数,所以要再用一个数组包装起来 • mainMethod.invoke(null, (Object)new String[] {"1","2","3"}); 把数组转换成Object,这样java就不会把它当成数组拆开
▸ 数组的反射
• 相同类型和纬度的数组属于同一个类 int[] a1 = new int[] {1, 2, 3}; System.out.println(a1.getClass().getName()); 输出的是:[I [表示数组I表示整数 System.out.println(a1.getClass().getSuperclass().getName()); 输出的是java.lang.Object • Object[] obj3 = a1; 会编译错误,因为a1中的成员类型是int,不是Object Arrays.asList() 接收的参数是Object[],a1的成员类型是int,使用该方法只会把整个数组看成一个对象,打印结果是a1的地址 ▸ 利用反射操作数组:自定义打印数组的方法 • getClass() isArray() Array.getLength(obj) Array.get(obj, i) • private static void 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数组,这样数组中可以存放不同类型的元素 a[0].getClass().getName();
▸ ArrayList与HashSet的比较
ReflectTest2.java • ArrayList是有顺序的集合,存放的是引用变量,不同引用变量可以引用同一个对象 HashSet添加对象时如果集合中已经有相同对象则不会放入 • HashSet存放对象时调用equals方法,比较的是HashCode 如果有两个对象值相同,由于HashCode不同,不会当成相同的对象 如果要让HaseSet认为两对象相等,要重写equals和hashCode方法 注意hashCode方法只在HashSet中才有价值,如果在ArrayList中则没有用 • 当对象存入HashSet后,不要改变对象的字段,因为字段改变了hashCode也变了,这样如果要remove该对象,由于哈希值不同,remove会失败 这样对象没有删除成功,还会存在内存中,会造成内存泄露 ———— 面试:java有内存泄露!HashSet中remove对象失败 而ArrayList就没有这个问题
▸ 反射的作用 -> 实现框架功能
• 框架的类比用户写的类先存在 • 框架:调用我写的类 工具:我调用工具类 ▸ 在配置文件(config.properties)中读取类名,再用反射调用该类(Collection) • config.properties中存储的是键值对 • 用Properties的load方法通过InputStream读取文件: InputStream ups = new FileInputStream(“”); props.load(ips); • 用Properties的getPropertiy()方法获取属性 String className = prop.getProperty(“className”); Collection collection = (Collection)Class.forName(className).newInstance();