首页 > 代码库 > 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,这个数组参数是一个单独参数,不用拆分。