首页 > 代码库 > Java反射再学习
Java反射再学习
在最初学习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 * 获得成员变量 —— name13 * 由于我们在Peron类中没有提供name的getter&setter方法14 * 所以直接使用下面的方式进行赋值15 */16 Field fn = clazz.getField("name");17 /*18 * 为name赋值19 * 赋值方法需要传入name成员变量所在的类对象20 * 所以我们在前面创建了Person类对象p21 */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方法将访问权限改为true38 fa.setAccessible(true);39 //反射是通用的一套技术,所以这里get方法默认返回Object类型,需要强制转换为int40 int age = (int) fa.get(p);41 System.out.println(age); //输出 2042 43 //最后调用被private修饰的say()方法打印出姓名和年龄44 Method say = clazz.getDeclaredMethod("say");45 say.setAccessible(true);46 say.invoke(p); //输出 Jack >>>>> 2047 }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 arguments10 */11 //m.invoke(a, strs);12 13 //正确的赋值操作如下14 m.invoke(a, (Object) strs); 15 }16 17 }
为什么会报错提示参数数量错误呢?其实这个一个向后兼容的天坑。在jdk 1.4的时候,当传入一个数组参数进入方法时,程序会自动将数组中的元素拆分出来当做多个参数进行操作。而在jdk 1.5开发,数组参数被规定为单独的一个参数,所以就产生了这个问题。所以,只需要将数组参数强制转换为Object类型就可以解决了,这样就等于是直接告诉JVM,这个数组参数是一个单独参数,不用拆分。