1.什么是反射
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。
2.反射能做什么
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
3.反射的具体实现
下面是一个基本的类 Person
package com.ys.reflex; public class Person { //私有属性 private String name = "Tom"; //公有属性 public int age = 18; //构造方法 public Person() { } //私有方法 private void say(){ System.out.println("private say()..."); } //公有方法 public void work(){ System.out.println("public work()..."); } }
得到class的三种形式
//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object // 类型的对象,而我不知道你具体是什么类,用这种方法 Person p1 = new Person(); Class c1 = p1.getClass(); //2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高 // 这说明任何一个类都有一个隐含的静态成员变量 class Class c2 = Person.class; //3、通过 Class 对象的 forName() 静态方法来获取,用的最多, // 但可能抛出 ClassNotFoundException 异常 Class c3 = Class.forName("com.ys.reflex.Person"); 需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true
②、通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
查阅 API 可以看到 Class 有很多方法: getName():获得类的完整名字。 getFields():获得类的public类型的属性。 getDeclaredFields():获得类的所有属性。包括private 声明的和继承类 getMethods():获得类的public类型的方法。 getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类 getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。 getConstructors():获得类的public类型的构造方法。 getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。 newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
//获得类完整的名字 String className = c2.getName(); System.out.println(className);//输出com.ys.reflex.Person //获得类的public类型的属性。 Field[] fields = c2.getFields(); for(Field field : fields){ System.out.println(field.getName());//age } //获得类的所有属性。包括私有的 Field [] allFields = c2.getDeclaredFields(); for(Field field : allFields){ System.out.println(field.getName());//name age } //获得类的public类型的方法。这里包括 Object 类的一些方法 Method [] methods = c2.getMethods(); for(Method method : methods){ System.out.println(method.getName());//work waid equls toString hashCode等 } //获得类的所有方法。 Method [] allMethods = c2.getDeclaredMethods(); for(Method method : allMethods){ System.out.println(method.getName());//work say } //获得指定的属性 Field f1 = c2.getField("age"); System.out.println(f1); //获得指定的私有属性 Field f2 = c2.getDeclaredField("name"); //启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消 f2.setAccessible(true); System.out.println(f2); //创建这个类的一个对象 Object p2 = c2.newInstance(); //将 p2 对象的 f2 属性赋值为 Bob,f2 属性即为 私有属性 name f2.set(p2,"Bob"); //使用反射机制可以打破封装性,导致了java对象的属性不安全。 System.out.println(f2.get(p2)); //Bob //获取构造方法 Constructor [] constructors = c2.getConstructors(); for(Constructor constructor : constructors){ System.out.println(constructor.toString());//public com.ys.reflex.Person() } 4.根据反射获取父类属性 父类 Parent.java public class Parent { public String publicField = "parent_publicField"; protected String protectField = "parent_protectField"; String defaultField = "parent_defaultField"; private String privateField = "parent_privateField"; } 子类 Son.java public class Son extends Parent { } 测试类: public class ReflectionTest { @Test public void testGetParentField() throws Exception{ Class c1 = Class.forName("com.ys.model.Son"); //获取父类私有属性值 System.out.println(getFieldValue(c1.newInstance(),"privateField")); } public static Field getDeclaredField(Object obj,String fieldName) { Field field = null; Class c = obj.getClass(); for(; c != Object.class ; c = c.getSuperclass()){ try { field = c.getDeclaredField(fieldName); field.setAccessible(true); return field; }catch (Exception e){ //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。 //如果这里的异常打印或者往外抛,则就不会执行c = c.getSuperclass(),最后就不会进入到父类中了 } } return null; } public static Object getFieldValue(Object object,String fieldName) throws Exception{ Field field = getDeclaredField(object,fieldName); return field.get(object); } }
示例1
public static void main(String args[]) { try { Class cls = Class.forName("MyTest.ConstructorDemo"); Class partypes[] = new Class[2]; partypes[0] = Integer.TYPE; partypes[1] = Integer.TYPE; Method meth = cls.getMethod("add", partypes); ConstructorDemo methobj = new ConstructorDemo(); Object arglist[] = new Object[2]; arglist[0] = new Integer(37); arglist[1] = new Integer(47); Object retobj= meth.invoke(methobj, arglist); Integer retval = (Integer)retobj; System.out.println(retval.intValue()); } catch (Throwable e) { System.err.println(e); } }
示例2
public static void main(String args[]) { try { Class clz = Class.forName("java.lang.String"); System.out.println("开始:" + clz.getName()); Field field [] = clz.getDeclaredFields(); System.out.println("Field开始:"); for (Field f : field){ System.out.println(":" + f.getName() + ":" + f.toString()); } System.out.println("Field结束:"); Method method []= clz.getDeclaredMethods(); System.out.println("Method开始:"); for (Method m : method){ System.out.println(":" + m.getName()); } System.out.println("Method结束:"); } catch (Throwable e) { System.err.println(e); } }
示例3
public int add(int a, int b) { return a + b; } public public static void main(String args[]) { try { Class clz = Class.forName("MyTest.ConstructorDemo"); Class partypes[] = new Class[2]; partypes[0] = Integer.TYPE; partypes[1] = Integer.TYPE; Method met = clz.getMethod("add", partypes); Constructor constructor = clz.getConstructor(); Object con = constructor.newInstance(); Object obj[] = new Object[2]; obj[0] = new Integer(10); obj[1] = new Integer(10); Object obj1 = met.invoke(con, obj); Integer retval = (Integer) obj1; System.out.println(":" + retval.intValue()); } catch (Throwable e) { System.err.println(e); } }
示例4
public static void main(String[] args) { ArrayList<Consumer> arr = new ArrayList<Consumer>(); ArrayList<Producer> arr1 = new ArrayList<Producer>(); Class clz1 = arr.getClass(); Class clz2 = arr1.getClass(); System.out.println(clz1==clz2); //true System.out.println(clz1.getName()); //类全名 System.out.println(clz2.getSimpleName()); //只有类名 }
示例5
Modifier.isAbstract(int modifiers) Modifier.isFinal(int modifiers) Modifier.isInterface(int modifiers) Modifier.isNative(int modifiers) Modifier.isPrivate(int modifiers) Modifier.isProtected(int modifiers) Modifier.isPublic(int modifiers) Modifier.isStatic(int modifiers) Modifier.isStrict(int modifiers) Modifier.isSynchronized(int modifiers) Modifier.isTransient(int modifiers) Modifier.isVolatile(int modifiers)
===========================================================================================
-
Package Info
你可以像这样从Class对象中获取包的相关信息: Class aClass = ... //obtain Class object. See prev. section Package package = aClass.getPackage(); 从Package对象中,你可以访问像包名这样的信息。你也可以访问在classpath中的JAR包中Manifest文件所指定的该包的信息。例如,你可以在Manifest文件中指定包的版本号。你可以从java.lang.Package中阅读更多和Package类相关的信息。
===========================================================================================
-
Superclass
你可以用Class对象访问超类。如下: Class superclass = aClass.getSuperclass(); 超类的Class对象和其他类的Class对象一样,所以你也可以继续在超类上用类的反射。
===========================================================================================
-
Implemented Interfaces
可以得到一个给定类实现的接口的列表。如下: Class aClass = ... //obtain Class object. See prev. section Class[] interfaces = aClass.getInterfaces(); 一个类可以实现多个接口。因此会返回一个Class类型的数组。在Java反射机制中,接口也是由Class对象来表示的。 注:只有被给定类特别声明实现过的接口才会被返回。如果一个超类实现了一个接口,但是该类没有特别声明它已经实现了这个接口,那么这个接口不会出现在返回的数组中。即使该类实际上在它的超类中实现过该接口。 要获取一个给定类的完整的所实现接口的列表,你将不得不递归的去查询该类以及它的超类。
===========================================================================================
-
Constructors
你可以像这样访问一个类的构造函数: Constructor[] constructors = aClass.getConstructors(); 构造函数在 Java反射——构造函数 中会有详细介绍。
===========================================================================================
-
Methods
你可以像这样访问一个类的方法: Method[] method = aClass.getMethods(); 方法在 Java反射——方法 中会有详细介绍。
===========================================================================================
-
Fields
你可以像这样访问类的字段(成员变量): Field[] method = aClass.getFields(); 字段在 Java反射——字段 中会有详细介绍。
===========================================================================================
-
Annotations
你可以像这样访问类的注解: Annotation[] annotations = aClass.getAnnotations(); 注解在 Java反射——注解 中会有详细介绍。
========================================================================================
构造方法
使用Java反射,你可以在运行时检查类的构造函数和实例化对象。这是通过Java类java.lang.reflect.Constructor做的。本文将会更详细的介绍Java中的Constructor对象。下面是所涵盖的主题列表:
Obtaining Constructor Objects (获取Constructor对象) Constructor Parameters (Constructor的参数) Instantiating Objects using Constructor Object (通过Constructor对象实例化对象)
=====================================================================================================
-
Obtaining Constructor Objects
Constructor对象可以从Class对象中获取。下面是一个例子:
Class aClass = ...//obtain class object Constructor[] constructors = aClass.getConstructors();
数组Constructor[ ]将会存储在类中所有声明为public的构造函数的Constructor实例。 如果你知道你要访问的构造函数的精确参数类型,你可以这样做而不是获取所有的构造函数。这个例子返回给定类的一个public的且接收一个String类型的变量作为参数的构造函数:
Class aClass = ...//obtain class object Constructor constructor = aClass.getConstructor(new Class[]{String.class});
如果没有和给定的参数相匹配的构造函数,会抛出NoSuchMethodException异常。
=====================================================================================================
-
Constructor Parameters
你可以像这样读取一个给定的构造函数所接收的参数:
Constructor constructor = ... // obtain constructor - see above Class[] parameterTypes = constructor.getParameterTypes();
=====================================================================================================
-
Instantiating Objects using Constructor Object
你可以像这样实例化一个对象:
//get constructor that takes a String as argument Constructor constructor = MyObject.class.getConstructor(String.class); MyObject myObject = (MyObject) constructor.newInstance("constructor-arg1");
Constructor.newInstance( )方法接受不定个数的参数,但是你必须提供你调用的构造函数需要的每个参数。在这种情况下,调用接受一个String类型参数的构造函数,你必须提供一个String类型的参数。
========================================================================================
字段
使用java反射,你可以在运行时检查类的字段(成员变量)并且get/set它们的值。这些是通过Java类java.lang.reflect.Field做的。本文将会给出更详细的关于Field对象的信息。请记住也去查阅Sun的JavaDoc。下面是主题列表:
-
Obtaining Field Objects (获取Field对象)
-
Field Name (字段名)
-
Field Type (字段类型)
-
Getting and Setting Field Values (get/set字段的值)
========================================================================================
-
Obtaining Field Objects
从Class对象中获取Field对象。这里有一个例子:
Class aClass = ...//obtain class object Field[] methods = aClass.getFields();
数组Field[ ]将会存储在类中声明的所有为public的字段的Field对象。
如果你知道你想要访问的字段的字段名,你可以像这样来访问它:
Class aClass = MyObject.class Field field = aClass.getField("someField");
上面的例子将会返回Field实例,和下面MyObject声明的字段someField对应:
public class MyObject{ public String someField = null; }
如果不存在和方法getField( )接受的参数一致的字段,会抛出NoSuchFieldException异常。
========================================================================================
-
Field Name
一旦你获取了一个Field实例,你可以像这样通过Field.getName()方法取得字段名:
Field field = ... //obtain field object String fieldName = field.getName();
========================================================================================
-
Field Type
你可以通过Field.getType() 方法取得字段类型(String, int etc.) :
Field field = aClass.getField("someField"); Object fieldType = field.getType();
========================================================================================
-
Getting and Setting Field Values
一旦你获取了Field对象的引用,你可以像这样通过Field.get()方法和Field.set()方法get/set字段的值:
Class aClass = MyObject.class Field field = aClass.getField("someField"); MyObject objectInstance = new MyObject(); Object value = field.get(objectInstance); field.set(objetInstance, value);
传给get和set方法的参数objectInstance必须是拥有该字段的实例。在上面的例子中,用了一个MyObject的实例,因为someField是类MyObject的成员实例。 如果字段是静态字段(public static …),则传null作为get和set方法的参数,而不是上面传的objectInstance。
========================================================================================
方法
使用Java反射,你可以检查类的方法并在运行时调用它们。这是通过Java类java.lang.reflect.Method做的。本将会比较详细的讲述Java的Method对象。下面是涵盖的主题列表:
-
Obtaining Method Objects (获取Method对象)
-
Method Parameters and Return Types (Method的参数和返回值类型)
-
Instantiating Objects using Constructor Object (使用构造函数对象实例化对象)
============================================================================================
-
Obtaining Method Objects
从Class对象中获取Method对象。这里有个例子:
Class aClass = ...//obtain class object Method[] methods = aClass.getMethods();
数组Method[ ]将存储类中声明的每个public方法的Method实例。
如果你知道你要访问的方法的准确的参数类型,你可以这样获取方法而不是所有的方法。这个例子返回一个方法名叫doSomething的public的方法,在给定的类中指定了接受一个String类型的参数:
Class aClass = ...//obtain class object Method method = aClass.getMethod("doSomething", new Class[]{String.class});
如果没有和给定参数相匹配的的方法,会抛出NoSuchMethodException异常。 如果你尝试去访问的方法没有参数,传入null作为参数,像这样:
Class aClass = ...//obtain class object Method method = aClass.getMethod("doSomething", null);
============================================================================================
-
Method Parameters and Return Types
你可以像这样读取一个给定的方法所接收的参数:
Method method = ... // obtain method - see above Class[] parameterTypes = method.getParameterTypes();
你可以像这样访问一个方法的返回值类型:
Method method = ... // obtain method - see above Class returnType = method.getReturnType();
============================================================================================
-
Invoking Methods using Method Object
你可以像这样调用一个方法:
//get method that takes a String as argument Method method = MyObject.class.getMethod("doSomething", String.class); Object returnValue = method.invoke(null, "parameter-value1");
参数null是你想要调用方法的对象。如果是静态方法,你需要提供null作为参数而不是一个对象的实例。在这个例子里,如果doSomething(String.class)不是静态的,你需要提供validMyObject作为实例而不是null。
方法Method.invoke(Object target, Object … parameters)接受不定个数的参数, 但是你必须提供与你所调用的方法对应的每个参数。在这里,这个方法接受一个String类型的参数,所以必须提供一个String类型的参数。
Getter和Setter方法
使用Java反射你可以检查类的方法并在运行时调用它们。这个可以用来检测一个给定类所包含的Getter和Setter方法。你不能明确的获取Getter和Setter,所以你不得不通过扫描类里面的所有方法,然后检查每个方法是不是Getter方法或者Setter方法。 首先,让我们建立一些描述Getter和Setter特征的规则:
-
Getter
一个getter方法的方法名易“get”开头,不接受参数,返回一个值。
-
Setter
一个setter方法的方法名以“set”开头,接受一个参数。 setter方法既可以返回值也可以不返回值。一些setter方法返回void,一些返回传入的值,其他的为了是使用方法链返回调用该setter的对象。因此,你不应该对一个setter方法的返回值类型做假设。 这里是找出一个类的getter方法和setter方法的代码示例:
public static void printGettersSetters(Class aClass){ Method[] methods = aClass.getMethods(); for(Method method : methods){ if(isGetter(method)) System.out.println("getter: " + method); if(isSetter(method)) System.out.println("setter: " + method); } } public static boolean isGetter(Method method){ if(!method.getName().startsWith("get")) return false; if(method.getParameterTypes().length != 0) return false; if(void.class.equals(method.getReturnType()) return false; return true; } public static boolean isSetter(Method method){ if(!method.getName().startsWith("set")) return false; if(method.getParameterTypes().length != 1) return false; return true;
} 私有变量和私有方法
尽管普遍的观点是不能直接访问私有字段和私有方法的,实际上通过Java反射是可以访问其他类的私有字段和私有方法的。它甚至不是那么困难。在单元测试期间很容易使用。本文将介绍你怎么做。 注:这个只会作用在运行独立的Java应用程序时,如写单元测试和常规应用。如果你尝试在Java Applet内使用它,你需要处理好SecurityManager。但是,因为这不是你经常要做的事情,所以在本文中将不会涉及它。 下面是本文的主题列表:
-
Accessing Private Fields (访问私有字段)
-
Accessing Private Methods (访问私有方法)
===========================================================================================
-
Accessing Private Fields
为了访问私有字段,你需要调用Class.getDeclaredField(String name)方法或者Class.getDeclaredFields()方法。Class.getField(String name)和Class.getFields()方法只会返回public的字段,所有它们不会工作。下面是一个简单的例子,通过Java反射去访问一个类的私有字段:
public class PrivateObject { private String privateString = null; public PrivateObject(String privateString) { this.privateString = privateString; } } PrivateObject privateObject = new PrivateObject("The Private Value"); Field privateStringField = PrivateObject.class.getDeclaredField("privateString"); privateStringField.setAccessible(true); String fieldValue = (String) privateStringField.get(privateObject); System.out.println("fieldValue = " + fieldValue);
这段代码示例将会打印出文本“fieldValue = The Private Value”,是PrivateObject实例在代码示例最开始赋值给私有字段privateString的。 注意这里使用的方法PrivateObject.class.getDeclaredField("privateString")。是这个方法调用返回了私有字段的值。这个方法只会返回在给定类里声明的字段的值,而不是其他任何在超类里声明的字段的值。 注意字体加粗的一行。仅仅针对反射,通过调用Field.setAccessible(true)方法,关闭了对特定的Field实例的访问检查。现在你可以访问它,尽管它是private,或者protected,或者是package scope,即使调用者不在这个范围内。你仍然不能通过一般的代码去访问这些字段。编译器不允许这样干。 ===========================================================================================
-
Accessing Private Methods
为了访问私有方法,你需要调用Class.getDeclaredMethod(String name, Class[ ] parameterTypes)方法或者Class.getDeclaredMethods()方法。Class.getMethod(String name, Class[ ] parameterTypes)方法和Class.getMethods()只会返回共有的方法,所以它们不会工作。下面是一个简单的代码示例,通过Java反射访问一个类的私有方法: public class PrivateObject {
private String privateString = null; public PrivateObject(String privateString) { this.privateString = privateString; } private String getPrivateString(){ return this.privateString; } } PrivateObject privateObject = new PrivateObject("The Private Value"); Method privateStringMethod = PrivateObject.class.getDeclaredMethod("getPrivateString", null); privateStringMethod.setAccessible(true); String returnValue = (String) privateStringMethod.invoke(privateObject, null); System.out.println("returnValue = " + returnValue);
这段代码示例将会打印出文本“returnValue = The Private Value”,是在代码示例最开始创建的PrivateObject实例调用getPrivateString()方法时的返回值。
注意这里使用的方法PrivateObject.class.getDeclaredMethod("privateString")。是这个方法调用返回的私有方法。这个方法只会返回在给定的类里面声明的方法,而不是在任何超类里声明的方法。 注意字体加粗的一行。仅仅针对反射,通过调用Method.setAccessible(true)方法,关闭了对特定的Method实例的访问检查。现在你可以访问它了,尽管它是private,或者protected,或者package scope,即使调用者不在这个范围内。你仍然不能通过一般的代码访问这些方法。编译器不允许这样干。
========================================================================================
注解
【译】8. Java反射--注解www.cnblogs.com
泛型
【译】9. Java反射--泛型www.cnblogs.com
数组
【译】10. Java反射--数组www.cnblogs.com
动态加载
【译】11. Java反射--动态代理www.cnblogs.com
动态加载和重新加载
【译】12. Java反射--类的动态加载和重新加载www.cnblogs.com