Reflection(反射)是Java程序开发语言的特征之一,它允许运行中的 Java程序对自身进行检查,或者说"自审",并能直接操作程序的内部属性。例如,使用它能获得 Java类中各成员的名称并显示出来。 Java的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C或者 C++ 中就没有办法在程序中获得函数定义相关的信息。反射机制是构建框架技术的基础所在,灵活掌握Java反射机制,对大家以后学习框架技术有很大的帮助。
反射的应用
Java的反射机制让它知道类的基本结构,这种“自审”的能力应用很广泛。Eclipse大家都用过,它有一个很强大的功能:我们在编写代码的时候,构建一个对象,要去调用该对象的方法和属性的时候。按下Alt+/(默认的快捷键)就会列出该对象可以调用的方法以及方法的参数等,更细致的还可以得到该方法具体的描述,这个强大的功能在使用eclipse的时候表现非常突出,这种技术就是运用了Java的反射机制来实现的。
认识一下Class类
与Java关键字class不同的是,java.lang.Class是一个具体的类,表示了所有的Java类,是Java反射机制的基础。
Class
没有公共构造方法。Class
对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass
方法自动构造的。获取一个Class对象有三种方法,下面通过一个实例来认识一下:
class Reflection { public static void main(String[] args) { // Class cla1 = new Class(); // Class类没有可见的构造方法,创建Class对象方法有下面三种 String str = "string"; Class cla1 = str.getClass(); Class cla2 = String.class; Class cla3 = null; try { cla3 = Class.forName("java.lang.String"); } catch(ClassNotFoundException e) { e.printStackTrace(); } /*比较通过不同方法得到的Class对象是否相同*/ System.out.println(cla1 == cla2); System.out.println(cla1 == cla3); /*打印Class对象所表示的实体的字符串表示形式*/ System.out.println(cla1.getName()); System.out.println(cla1.getPackage().toString()); System.out.println(cla2); System.out.println(Class.class.getName()); } } /** 打印结果 true true java.lang.String java.lang.Class */
总结:
根据不同的情况使用不同的方法,三种方法应用情景是各不相同的。
存在对象的时候使用obj.getClass()
没有对象的时候直接在类名后加class字符
上面两种情况JVM都加载了类,如果JVM尚未加载该类,那么就使用Class.forName方法
打印结果的第一二行说明上面三个创建Class对象的方法得到的Class对象是同一个
获得了Class对象,可以做什么?
既然拿到了Class对象,那么我们可以利用其做什么呢?
获取该Class对象所表示的类或接口的Constructor(构造器)、Annotation(注释)、Field(成员变量)、Method(方法)、newInstance(创建对象)等等。
我们下面就来看看如何利用这些方法吧。
1, 判断一个对象是否属于一个类
class Person { } public class IsInstance { public static void main(String args[]) { try { Class cls = Person.class; boolean b1 = cls.isInstance(new String("abc")); System.out.println(b1); boolean b2 = cls.isInstance(new Person()); System.out.println(b2); } catch (Throwable e) { System.err.println(e); } } } /** 打印结果 false true */
在这个例子中创建了Person对象,调用Class对象的isInstance方法判断传入对象是否与此Class对象所表示的类或者其任一子类的实例,此方法等效于instanceof运算符
2, 创建类的对象
class Person { public static final int MAN = 1; public static final int WOMAN = 0; int sex; int age; public Person() { this.sex = MAN; this.age = 25; } public Person(int sex, int age) { this.sex = sex; this.age = age; } public int getSex() { return sex; } public int getAge() { return age; } } public class NewInstance { public static void main(String args[]) { try { Class cls = Person.class; Person person = (Person)cls.newInstance(); boolean bool = person instanceof Person; System.out.println(bool); System.out.println(person.getSex()); System.out.println(person.getAge()); } catch (Exception e) { e.printStackTrace(); } } } /** 打印结果 true 1 25 */
从打印结果上可以发现,Class对象调用newInstance方法返回一个该Class对象所表示的类的对象的引用,相当于new Person()。
3, 获取类的构造方法
import java.lang.reflect.*; class ConstructorTest { public static void main(String[] args) { try { Class clazz = Class.forName("java.lang.String"); Constructor cont = clazz.getConstructor(byte[].class); //直接打印其调用toString方法返回的字符串 System.out.println(cont); } catch(ClassNotFoundException e) { e.printStackTrace(); } catch(NoSuchMethodException e) { e.printStackTrace(); } } }
通过Class类的getConstructor和getConstructors方法可以得到Class对象所表示的类的公共构造方法的对象Constructor,通过该对象可以获得构造方法的名称、参数类型、还可以以此构造方法声明一个新的实例,并且可以指定指定的初始化参数初始化该实例。
4, 获取类中的方法,并 根据方法的名称来调用该方法
import java.lang.reflect.*; class Person { public static final int MAN = 1; public static final int WOMAN = 0; int sex; int age; public Person() { this.sex = MAN; this.age = 25; } public Person(int sex, int age) { this.sex = sex; this.age = age; } public int getSex() { return sex; } public int getAge() { return age; } } class GetMethod { public static void main(String[] args) { try { Class clazz = Person.class; Constructor cont = clazz.getConstructor(int.class, int.class); Person person = (Person)cont.newInstance(new Object[]{Person.WOMAN, 22}); Method mgetSex = clazz.getDeclaredMethod("getSex", new Class[0]); Method mgetAge = clazz.getDeclaredMethod("getAge", new Class[0]); int sex = (int)mgetSex.invoke(person, new Object[0]); int age = (int)mgetAge.invoke(person, new Object[0]); System.out.println("sex :" + sex); System.out.println("age :" + age); } catch(Throwable e) { e.printStackTrace(); } } } /** 打印结果 sex :0 age :22 */
上面的小程序先用Class对象创建了一个Person类的带参的构造方法并利用这个构造方法传入两个参数实例化了一个Person对象,然后用getDeclaredMethod分别获取了Person类中getSex和getAge两个方法的Method对象,最后通过这两个Method对象调用invoke方法
invoke(person, new Object[0]);
第一个参数指示调用底层方法的对象,第二的参数表示底层方法的形参数组,这里将数组长度设为0表示底层方法没有参数。
通过getMethod方法还可以获得Class对象表示的类或接口的超类或者超接口的方法。
6, 获取、修改类的成员变量
class Student { private int ID; private int grade; public Student() { this.ID = 130000; this.grade = 0; } public Student(int ID, int grade) { this.ID = ID; this.grade = grade; } public int getID() { return ID; } public int getGrade() { return grade; } } class GetMethod { public static void main(String[] args) { try { Student Tom = new Student(231058, 225); Student Karry = new Student(230542, 286); Class clazz = Tom.getClass(); Field field = clazz.getDeclaredField("grade"); field.setAccessible(true); System.out.println("Tom'grade :" + (int)field.get(Tom)); System.out.println("Karry'grade :" + (int)field.get(Karry)); field.set(Tom, 0); field.set(Karry, 511); //偷天换日,太黑了,哈哈... System.out.println("Changed...\n"); System.out.println("Tom'grade :" + (int)field.get(Tom)); System.out.println("Karry'grade :" + (int)field.get(Karry)); } catch(Throwable e) { e.printStackTrace(); } } } /** 打印结果 Tom'grade :225 Karry'grade :286 Changed... Tom'grade :0 Karry'grade :511 */
哈哈,这里我们模拟了一次“恶意”修改学分的事件,估计Tom看到自己的学分肯定要惊呆了。这里首先是获取一个指定字段的Field对象,这里有点需要注意的是,由于Student类中grade属性访问修饰符为private,所以这里必须要使用getDeclaredField而不能是getField,因为后者只能获取公共字段,也就是public修饰的字段。
8, 使用数组
import java.lang.reflect.*; class ReflectArray { public static void main(String[] args) { int array[] = new int[]{1, 4, 25, 88, 56, 57}; boolean bool[] = new boolean[]{true, true, false, true, false}; printArray(array); printArray(bool); reseveArray(array); reseveArray(bool); System.out.println("Reseved..."); printArray(array); printArray(bool); } public static void printArray(Object obj) { Class clazz = obj.getClass(); if(clazz.isArray()) { int len = Array.getLength(obj); for(int index = 0; index < len; index++) { System.out.print(Array.get(obj, index) + " "); } System.out.println(); } else { System.out.println(obj); } return; } public static void reseveArray(Object obj) { Class clazz = obj.getClass(); if(clazz.isArray()) { int end = Array.getLength(obj) - 1; /*将数组反转*/ for(int index = 0; index <= end; index++) { Object buff = Array.get(obj, index); Array.set(obj, index, Array.get(obj, end)); Array.set(obj, end, buff); end--; } } else { System.out.println("Error. Not a Array."); } return; } } /** 打印结果 1 4 25 88 56 57 true true false true false Reseved... 57 56 88 25 4 1 false true false true true */
上面简单的例子实现了打印数组中的值和将数组的值进行反转两个操作,先通过Class对象的isArray方法判断obj是不是一个数组,若是,则通过Array类获取或者改变数组中元素的值。
这里留下一个疑问就是能不能通过反射确定一个数组的类型呢?期待各位的见解。