标签:style blog color java 使用 io 文件 for
在最初学习Java的时候觉得反射真的好难,并不是技术负责,而是思想复杂,无法接受。随着工作经验的增多,今日偶然间又看见某智的一个视频,感觉茅塞顿开。顺便在此系统整理一下反射的知识。
一言以蔽之:反射就是将Java类的各个组成部分转换为对应的Java对象。
我们知道,一切皆对象,那么这个“一切”必然也包含了Java类啊,Java类也是一种事物,那么他是什么的对象呢?毫无疑问,Java类是Class类的对象。(PS:那么Class类又是谁的对象呢?求大神指教?这问题貌似无穷无尽啊 %>_<% )
换句话说,Class类对象代表的就是Java类编译后的那份字节码,类文件里面有的,字节码文件里面肯定有,所以通过Class来操作字节码,就相当于操作类文件,只是多了一个中间步骤而已。
所以,要使用反射技术,首先我们要在程序中拿到这份字节码,有三种方法:
-- 类名.class
--对象名.getClass()
-- Class.forName("完整包名")
1 public class Test { 2 public static void main(String[] args) { 3 Person p = new com.test.Person(); 4 Class clazz1 = Person.class; 5 Class clazz2 = p.getClass(); 6 Class clazz3 = Class.forName("com.test.Person"); 7 } 8 }
依据实际开发中,在使用反射的那个时间能够拿到的是类、对象还是包名来选择使用哪种方式加载字节码进入内存。
当我们拿到Class对象以后,也就是将字节码载入内存后,哇咔咔,我们就可以随便搞啦~~么么哒!
一个Java类里包含的主要东西有:
-- 包 Package
-- 成员变量 Field
-- 方法 Method
-- 构造方法 Constructor
-- 注解 Annotation
-- 类加载器 ClassLoader
-- 泛型相关(先忽略)
示例Person类如下:
1 package com.test.reflecttest; 2 3 public class Person { 4 5 public String name; 6 7 private int age; 8 9 public Person() { 10 super(); 11 } 12 13 public Person(String name, int age) { 14 super(); 15 this.name = name; 16 this.age = age; 17 } 18 19 public int getAge() { 20 return age; 21 } 22 23 public void setAge(int age) { 24 this.age = age; 25 } 26 27 private void say() { 28 System.out.println(this.name + " >>>>> " + this.age); 29 } 30 31 }
首先是拿到成员变量:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 Class<Person> clazz = Person.class; 5 //通过指定的成员变量名来获得一个成员变量对象 6 Field field = clazz.getField("name"); 7 //获得私有成员变量对象 8 Field field2 = clazz.getDeclaredField("age"); 9 //获得所有非私有成员变量对象 10 Field[] fields = clazz.getFields(); 11 //获得所有私有、非私有成员变量对象 12 Field[] fields2 = clazz.getDeclaredFields(); 13 } 14 15 }
从例子中可以看出,只有调用getDeclaredXxx()等类似方法才能够获取到私有类成员。
获得类方法的代码类似获得成员变量:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 Class<Person> clazz = Person.class; 5 //通过指定的方法名来获得一个方法对象 6 Method method = clazz.getMethod("setName"); 7 //获得所有非私有方法对象 8 Method[] methods = clazz.getMethods(); 9 //获得所有私有、非私有方法对象 10 Method[] methods2 = clazz.getDeclaredMethods(); 11 } 12 }
获得构造器并使用构造器创建对象的方法:
1 public class BlogTest { 2 3 public static void main(String[] args) throws Exception { 4 Class<Person> clazz = Person.class; 5 //获得无参构造器 6 Constructor<Person> c1 = clazz.getConstructor(); 7 //使用无参构造器创建类对象 8 Person p = c1.newInstance(); 9 //通过指定参数列表的参数类型来获得构造器 10 Constructor<Person> c2 = clazz.getConstructor(String.class, int.class); 11 //传入参数,使用有参构造器创建类对象 12 Person p2 = c2.newInstance("Lucas",20); 13 } 14 }
OK,通过上面三个例子我们已经知道了怎么获得类的构造器、成员变量、方法,现在使用这三者来做一个综合示例(预警:下面的示例极度无聊,祝各位安好)
示例说明:我们使用反射调用Person类的两个成员变量的setXxx方法为他们赋值,然后使用反射获得赋值后的成员变量值。
1 public class BlogTest { 2 3 public static void main(String[] args) throws Exception { 4 //获得Class对象,将Person类的字节码载入内存 5 Class<Person> clazz = Person.class; 6 //获得Person类的无参构造器 7 Constructor<Person> c = clazz.getConstructor(); 8 //使用无参构造器创建Person对象 9 Person p = c.newInstance(); 10 11 /* 12 * 获得成员变量 —— name 13 * 由于我们在Peron类中没有提供name的getter&setter方法 14 * 所以直接使用下面的方式进行赋值 15 */ 16 Field fn = clazz.getField("name"); 17 /* 18 * 为name赋值 19 * 赋值方法需要传入name成员变量所在的类对象 20 * 所以我们在前面创建了Person类对象p 21 */ 22 fn.set(p, "Jack"); 23 24 //获得成员变量age的setAge方法,程序通过参数列表来区分方法,所以传入与调用方法参数列表顺序一致的参数类型 25 Method ma = clazz.getMethod("setAge", int.class); 26 /* 27 * invoke方法用于执行调用者(调用者即某个要执行的方法) 28 * 需要传入执行的方法所属的类对象和参数列表 29 */ 30 ma.invoke(p, 20); 31 32 /* 33 * 使用反射获得赋值后的age成员变量的值 34 * 由于age是private修饰的,所以要使用getDeclaredXxx类似方法来获取 35 */ 36 Field fa = clazz.getDeclaredField("age"); 37 //对于所有被private修饰的类成员,要进行访问或操作,都需要调用setAccessible方法将访问权限改为true 38 fa.setAccessible(true); 39 //反射是通用的一套技术,所以这里get方法默认返回Object类型,需要强制转换为int 40 int age = (int) fa.get(p); 41 System.out.println(age); //输出 20 42 43 //最后调用被private修饰的say()方法打印出姓名和年龄 44 Method say = clazz.getDeclaredMethod("say"); 45 say.setAccessible(true); 46 say.invoke(p); //输出 Jack >>>>> 20 47 } 48 }
通过上面的例子,对于反射的基本应用差不多就搞定了。特别说明的是,对于被private修饰的类成员,在执行访问或操作时都需要调用其setAccessible(boolean flag);方法将该成员的访问权限设置为true。否则程序将抛出非法参数异常。
在使用反射调用方法时,特别注意,当被调用的方法接受的参数为数组时,需要特别处理。
我们提供一个类如下:这个类的test方法接受一个String类型的数组,并打印出所有数组元素。
1 public class ArrayMethod { 2 3 public void test(String[] strs) { 4 for (String s : strs) { 5 System.out.println(s); 6 } 7 } 8 }
我们现在使用反射调用这个方法,并为其传参:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 ArrayMethod a = new com.test.reflecttest.ArrayMethod(); 5 Method m = a.getClass().getMethod("test", String[].class); 6 String[] strs = new String[]{"abc","def","ghi","jkl"}; 7 /* 8 * 执行以下操作会抛出异常: 9 * java.lang.IllegalArgumentException: wrong number of arguments 10 */ 11 //m.invoke(a, strs); 12 13 //正确的赋值操作如下 14 m.invoke(a, (Object) strs); 15 } 16 17 }
为什么会报错提示参数数量错误呢?其实这个一个向后兼容的天坑。在jdk 1.4的时候,当传入一个数组参数进入方法时,程序会自动将数组中的元素拆分出来当做多个参数进行操作。而在jdk 1.5开发,数组参数被规定为单独的一个参数,所以就产生了这个问题。所以,只需要将数组参数强制转换为Object类型就可以解决了,这样就等于是直接告诉JVM,这个数组参数是一个单独参数,不用拆分。
标签:style blog color java 使用 io 文件 for
原文地址:http://www.cnblogs.com/jasongan/p/3887281.html