java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制Reflection。
这就说明:Java程序可以加载一个编译期间完全未知的class,获悉其完整构造,并生成其对象实体、或对其fields设值、或唤起其methods。虽然java并不是动态语言。
如何达到上述目的,是本文探讨的内容。本文将介绍Reflection APIs,java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等类。
java.lang.Class是反射入口。java中一切皆对象,继承自object。每一个对象都有自己的类型,通过getClass()得到一个java.lang.Class对象,(基本类型通过字面值.class获得,也叫类标记,如:int.class)这个Class对象中包含了与类型有关的信息,包括其字段、方法、父类或接口等。
在正式开始学习反射之前,我们来了解一些类的装载的知识。
类装载器把一个类装入JVM 中,要经过以下步骤:
1.装载:查找和导入Class 文件;
通过一个类的全限定名来获取定义此类的二进制字节流.然后将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构.最后在Java堆中生成一个代表这个类的java.lang.class对像,作为方法区的数据入口.
2.链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的:
a)校验:检查载入Class 文件数据的正确性;
b)准备:给类的静态变量分配存储空间;
c)解析:将符号引用转成直接引用;
3.初始化:对类的静态变量、静态代码块执行初始化工作。
网上找到一个例子,可以看到一个类在什么时候被初始化:
package study.javacore.test; import java.util.Random; import klg.utils.MyTestUtil; class Initable { static final int staticFinal = 47; static final int staticFinal2 = InitClassTest.rand.nextInt(100); static { System.out.println("Initialization Initable"); } } class Initable2 { static int staticNoFinal = 147; static { System.out.println("Initialization Initable2"); } } class Initable3 { static int staticNoFinal = 74; static { System.out.println("Initialization Initable3"); } } public class InitClassTest { public static Random rand = new Random(47); public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class clazz = Initable.class; // 不会引起初始化 MyTestUtil.print(clazz.getDeclaredFields());//读取字段信息不会初始化,获取方法信息也一样 //当然如果取得字段的值就已经是初化了,调用方法也一样。因为这些操作都需要一个对象。 //Initable initable=(Initable) clazz.newInstance(); //这个必然会导致Initable初始化 System.out.println("after creating Initable reference"); System.out.println(Initable.staticFinal); // 引用编译器常量不会引起初始化 System.out.println(Initable.staticFinal2); // 引起初始化 System.out.println(Initable2.staticNoFinal); // 引用非编译期常量会引起初始化 Class initable3 = Class.forName("study.javacore.test.Initable3"); // 默认会引起初始化 System.out.println("after creating Initable3 reference"); System.out.println(Initable3.staticNoFinal);// 前面已经初始化此处不用再初始化 } }
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
对于我们编写的每个类,它们都有一个Class 对象。(更恰当地说,是保存在一个完全同名的.class 文件中)。在运行期,一旦我们想生成那个类的一个对象,用于执行程序的Java 虚拟机(JVM)首先就会检查那个类型的Class 对象是否已经载入。若尚未载入,JVM 就会查找同名的.class 文件,并将其载入。所以Java 程序启动时并不是完全载入的,这一点与许多传统语言都不同。一旦那个类型的Class 对象进入内存,就用它创建那一类型的所有对象。
取得Class对象的句柄有三种方式:
1.Class.forName(className)
实际上是调 Class.forName(className, true, currentLoader)。注意第二个参数,是指Class被loading后必须被初始化。
2.实例对象.getClass()。
说明:对类进行静态初始化、非静态初始化;返回引用object运行时真正(子类对象的句柄可能会赋给父类对象的句柄<多态>)所属的类的Class的对象。
Person person=new Person(); Object op=person; System.out.println(op.getClass());//class study.javacore.test.Person
这里有一个特例,基本类型在向上类型转换以后,getClass()得到的是基本类型对应的包装类型。
int i=2; Object oi=i; System.out.println(oi.getClass());//class java.lang.Integer
3.类名.class,又叫类标记。
说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作。
这样做不仅更加简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以执行的效率也会更高。类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据类型的封装器类,它还存在一个名为TYPE 的标准字段:例如,int.class==Integer.TYPE
之所以说是句柄,是因为这三种方式得到Class对象是同一个:
System.out.println(Class.forName("java.lang.Object")==Object.class);//true System.out.println(Object.class==new Object().getClass()); //true
我们主要从4个方面认识反射的api
要记住一切都是由Class对象开始,java.lang.Class是反射入口。
我们可以通过一个类的Class对象了解该类的基本信息。
Java class 内部模块 |
Java class 内部模块说明 |
相应之Reflection API,多半为Class methods。 |
返回值类型(return type) |
package |
class隶属哪个package |
getPackage() |
Package |
field |
class的属性 按访问权限分为: 所有、可访问 |
getDeclaredFields() ; getDeclaredField(String name) ;
|
Field[] Field |
method |
class的方法 按访问权限分为: 所有、可访问
|
getMethods() ; getMethod(String name, Class<?>...parameterTypes); |
Method[] Method |
modifier |
class(或methods, fields)的属性 |
int getModifiers() Modifier.toString(int) Modifier.isInterface(int) |
int String bool |
class name or interface name |
class/interface |
名称getName() |
String |
type parameters |
参数化类型的名称 |
getTypeParameters() |
TypeVariable <Class>[] |
base class |
base class(只可能一个) |
getSuperClass() |
Class或null |
implemented interfaces |
实现有哪些interfaces |
getInterfaces() |
Class[] |
inner classes |
内部classes |
getDeclaredClasses() |
Class[] |
outer class |
如果我们观察的class 本身是inner classes,那么相对它就会有个outer class。 |
getDeclaringClass() |
Class |
下面我们来实践一下:
Person.java
class Person { private int age; private String name; public Person() { super(); } public Person(int age, String name) { this.age = age; this.name = name; } public void say(){ System.out.println("Person say!"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } }
测试:注:MyTestUtil是我用反射写的用于打印对象的属性值工具类。
public static void main(String[] args) { Class demo1 = null; try { demo1 = Class.forName("study.javacore.test.Person"); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } Class demo = Person.class; System.out.println(demo1 == demo);// true System.out.println(demo.getPackage());// 包名 System.out.println(demo.getModifiers());// 访问权限修饰符,0就是没有 MyTestUtil.print(demo.getConstructors());// 构成方法 MyTestUtil.print(demo.getDeclaredFields());// 字段信息 MyTestUtil.print(demo.getMethods());// 方法信息 }
获取累的实例有两种方法:
try { // 调用的是无参的构成方法,如果没有无参数的构造方法报错。 Person person =(Person) demo.newInstance(); person.setAge(10); person.setName("klguang"); MyTestUtil.printWithSign("person", person); } catch (Exception e) { System.out.println("Class newInstance wrong!!!"); e.printStackTrace(); }
try { Class[] types = new Class[] { int.class, String.class }; Constructor<Person> constructor = demo.getConstructor(types); Person person2 = constructor.newInstance(20, "klguang"); MyTestUtil.printWithSign("person2", person2); } catch (Exception e) { System.out.println("::constructor wrong!!!"); e.printStackTrace(); }
可通过Class对象的getDeclaredFields() 或getDeclaredField(String name) 方法取得字段Field对象,然后调用field.setAccessible(true),允许访问字段,最后用field.set(Object obj,Object value)或field.get(Object obj)来设置和获取字段的值。
Person person3=new Person(20,"klguang"); Field[] fileds = demo.getDeclaredFields(); for (Field field : fileds) { field.setAccessible(true);// 设置些属性是可以访问的 String name=field.getName();//取得field的名称 try { Object value = field.get(person3);// 得到此属性的值 System.out.println("fieldName:"+name+"\tfieldValue:"+value); if(name.equals("age")) field.set(person3, 40); } catch (Exception e) { e.printStackTrace(); } } MyTestUtil.printWithSign("person after access filed", person3);
可通过Class对象的getMethods() 或getMethod(String name, Class<?>...parameterTypes)方法取得Method对象,通过method.invoke(Object obj, Object... args)调用obj对象的方法。
Person person4=new Person(20,"klguang"); Method methods[] = demo.getMethods(); for (java.lang.reflect.Method method : methods) { if (method.getName().startsWith("get")) { try { Object value=method.invoke(person4, null); System.out.println("method invoke , return value is "+value); } catch (Exception e) { System.err.println("method invoke wrong!!"); } } }
我用反射写了一个打印对象的属性值工具类MyTestUtil,在控制台格式化输出对象属性。因此,可以不用在以后测试javabean时候,复写toString();当然复写了更好。
源码下载 MyTestUtil http://files.cnblogs.com/files/klguang/MyTestUtil.rar
该工具类用到的反射方法如下:
/** * * * @param object * @param recursion * 是否要递归 * @return */ private static String beanToStr(Object object, boolean recursion) { if (object == null) return "null"; Class clazz = object.getClass(); StringBuilder sb = new StringBuilder(); //返回源代码中给出的底层类的简称 sb.append(clazz.getSimpleName()).append("["); Field[] fields = sortFieldByType(clazz.getDeclaredFields()); int iMax = fields.length - 1; if (iMax == -1) return sb.append("]").toString(); for (int i = 0;; i++) { Field field = fields[i]; field.setAccessible(true);// 设置些属性是可以访问的 String name = field.getName();// 取得field的名称 if (name.equals("serialVersionUID")) continue; try { Object value = field.get(object);// 得到此属性的值 if (isSimpleType(value) || !recursion) sb.append(name + " = " + String.valueOf(value)); else sb.append("\r\n" + indent(clazz.getSimpleName().length() + 2," ") + objToStr(value, false) + "\r\n"); } catch (Exception e) { e.printStackTrace(); } if (i == iMax) return sb.append("]").toString(); sb.append(","); } }