Technorati 标记:  , , ,

    承接前篇对java Reflection的介绍 :

     , 详细讲解了四种获取Class对象的方法;

     , 详细讲解了如果获取class对象的信息,包括class对象的信息,class对象的成员信息。class对象的信息主要有修饰符,泛型参数,所实现的接口,继承的路径与及注解信息。class对象的成员信息主要有成员变量field,函数方法与构造器。

    这一接将继续探讨如何操纵Member。java Reflection 定义了一个接口Member,而它的实现就包括了Field、Method、Constructor。这一次将探讨如何如何使用这三个实现和其相关的API;

一、Field

    field包括了类型(type)和值(value)。Field提供提供相关的方法访问field类型和获取/设置filed的值。

    (1)访问field的类型

    曾经谈过,在java里,一个field要不就是8种基本的数据类型之一,要不就是一个引用。基本数据类型(boolean,byte,short,int,float,long,double,char);“引用”指的就是直接或者间接继承Object的类,同时还包括了接口(interface)、数组(arrays)、枚举(enum)。下面给出一个Demo,FieldDemo,演示如何获取field的类型。

public class FieldDemo
{ public int id = 1; public String name = "TianYa"; public double[] d; public List
lo; public T val; public static void main(String[] args) throws Exception{ Class
c = FieldDemo.class; Field field = c.getField("id");// Field field = c.getField("name");// Field field = c.getField("d");// Field field = c.getField("lo");// Field field = c.getField("val"); out.println("field 类型 " + field.getType()); out.println("field 泛型:" + field.getGenericType());; }}

    使用field.getType()即可获取field类型,类似的field.getGenericType()可获取field的泛型类型,不同的是,当field不具有泛型参数时,getGenericType()会返回其field类型。以下是部分输出:

    大致的输出应该没有太多的疑问,在这里还要注意一下,“T”输出的field类型:Object,这是因为java 泛型的类型擦出导致的—在编译的时候不包含泛型信息,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。可以查阅相关资料,了解更多类型擦除的信息。

    在这里,可能还会有一个疑问,就是在FieldDemo里的field都是public的,那是否可以为private的??当然是可以的,可以使用filed.getDeclaredField(String name)获取,private修饰的field,更多的信息可以参考。

    (2)获取/设置field值

    对于一个实例对象,可以通过反射来设置其值。在非必要的情况下,尽量不要使用反射来设置对象的值,因为这违反类的设计原则(封装性)。不过若你想深入了解java,反射是必须掌握的基础,因为其在各种架构里都会用到。

    Field对象提供了如下两组方法读取或设置Field值。

  • getXxx(Object obj):获取obj对象该字段的属性值。此处的Xxx对象8个基本类型,如果该属性是引用类型,则取消get后面的Xxx。
  • setXxx(Object obj, Xxx val):将obj对象该字段设置成val值。此处的Xxx对象8个基本类型,如果该属性是引用类型,则取消set后面的Xxx。

    在修改一些private field、没有访问权限或者final的field时,需要调用setAccessible(true)方法取消java语言的访问权限检查,否则会抛出IllegalAccessException。

    Demo People演示了如何去修改基本数据类型,数组,枚举的值。

enum Sex{MALE,FEMALE}public class People {	private int id = 1;	private String name = "zhang san";	private Sex sex = Sex.MALE;	private String[] friends = {"李四","王五"};		public static void main(String[] args) throws Exception{		People p = new People();		Class
c = p.getClass(); //------------------------------- Field Id = c.getDeclaredField("id"); System.out.println("id原来的值:" + p.id); Id.setInt(p, 2); System.out.println("id改动后的值:" + p.id); //--------------------------------- Field gender = c.getDeclaredField("sex"); System.out.println("sex原来的值:" + p.sex); gender.set(p, Sex.FEMALE); System.out.println("sex改动后的值:" + p.sex); //---------------------------------- Field fri = c.getDeclaredField("friends"); System.out.println("friends原来的值:" + Arrays.asList(p.friends)); String[] newFriends = {"赵六","小七"}; fri.set(p, newFriends); System.out.println("friends改动后的值:" + Arrays.asList(p.friends)); }}

    其输出如下:

    细心的你,也许会发现,在People里修改了private 修饰的field(id,name…),可却没有抛出异常??为什么呢??不是说修改private filed时需要取消访问权限吗??问题出现在,把main函数也写在People类里了,如果把main函数写在一个类,还可以直接使用类对象访问field值(如People类里的p.id,而不需要调用get/set方法来访问。java定义里一样有:不能通过类对象直接访问field。),在此反射也是一样的道理。看一下FieldException ,类外的调用是需要使用方法setAccessable(true)来取消访问检查权限的,否则否抛出illegalAccessException。

public class FieldExceptionTest {	private boolean b = true;	public boolean isB() {		return b;	}}

 

public class Test {		public static void main(String[] args) throws Exception{		FieldExceptionTest ft = new FieldExceptionTest();		Class
c = ft.getClass(); Field f = c.getDeclaredField("b");// f.setAccessible(true); //取消访问权限 f.setBoolean(ft, Boolean.FALSE); //IllegalAccessException System.out.println(ft.isB()); }}
    不调用setAccessible(true)会抛出illegalAccessException。

 

    (3)检索和解释field修饰符

    除了可以获取field的类型,还可以检索出field的修饰符,field修饰符主要包括:public,protected,private,transient,volatile,static,final和注解,需要了解修饰符更多的信息,可以参考。

    可以使用field.getModifiers()获取field修饰符,而getModifiers()返回是特定的字节码。需要使用Modifier.toString()来解释以获取string类型的修饰符。想了解Modifier.tuString(int mod)的实现的,可以点击 ,。Demo FieldModifier演示如何获取指定修饰符的field,同时也增加了一些判断,field.isSynthetic()判断field是否由编译器生成的,field.isEnumConstant()可判断field是否为枚举常量。

enum Gender {	MALE, FEMALE}public class FieldModifier {	private int id;	public String name;	public static void main(String[] args) {		Class
c = Gender.class; int searchMods = 0x0; //指定修饰符 String[] midifiers = { "public" }; for (int i = 0; i < midifiers.length; i++) { searchMods |= modifierFromString(midifiers[i]); } Field[] flds = c.getDeclaredFields(); out.println("包含指定修饰符 " + Modifier.toString(searchMods) + "的field:"); boolean found = false; //判断是否找到指定修饰符的field for (Field f : flds) { int foundMods = f.getModifiers(); //要求包含所有指定修饰符 if ((foundMods & searchMods) == searchMods) { out.println(f.getName() + " synthetic?= " + f.isSynthetic() + "; 枚举常量: " + f.isEnumConstant()); found = true; } } if (!found) { out.println("没有找到指定的field"); } } private static int modifierFromString(String s) { int m = 0x0; if ("public".equals(s)) m |= Modifier.PUBLIC; else if ("protected".equals(s)) m |= Modifier.PROTECTED; else if ("private".equals(s)) m |= Modifier.PRIVATE; else if ("static".equals(s)) m |= Modifier.STATIC; else if ("final".equals(s)) m |= Modifier.FINAL; else if ("transient".equals(s)) m |= Modifier.TRANSIENT; else if ("volatile".equals(s)) m |= Modifier.VOLATILE; return m; } }

    其输出如下:

   在FieldModifier里作如下修改,找出FieldModifier里private修饰的field

Class
c = FieldModifier.class;//指定修饰符String[] midifiers = { "private" };

    输出如下:

    类似的有:

    再看如下修改:

Class
c = Gender.class;//指定修饰符String[] midifiers = { "private","static","finale" };
    其输出如下:

    发现输出了一些并没有在Gender定义的field,这些都是由编译器生成的,供运行时用的field

 

二、Method

    (1)在一个方法里,可以包含方法名,修饰符,返回类型,参数,注释符,或者异常。相对应地,java.lang,reflect.Method提供了各种API来获取这些信息,同时也提供了调用此方法的函数invoke()。Demo MethodTest演示了如何获取Method的信息:

public class MethodTest {	public static void main(String[] args) throws Exception {		Class
c = Class.forName("java.io.PrintStream"); Method[] allMethods = c.getDeclaredMethods(); for (Method m : allMethods) { if (!m.getName().equals("printf")) { continue; } out.println("函数的泛型字符串表示:"); out.println(m.toGenericString());//也可使用toString() out.println("返回值类型(泛型):"); out.println(m.getReturnType()); out.println(m.getGenericReturnType() + "(泛型)"); Class
[] pType = m.getParameterTypes(); Type[] gpType = m.getGenericParameterTypes(); out.println("参数:"); for (int i = 0; i < pType.length; i++) { out.println(pType[i]); out.println(gpType[i] + "(泛型)"); } Class
[] eType = m.getExceptionTypes(); Type[] geType = m.getGenericExceptionTypes(); out.println("异常:"); for (int i = 0; i < eType.length; i++) { out.println(eType[i]); out.println(geType[i] + "(泛型)"); } } }}

    首先来解释一下,方法里带有”Generic”字样的,表示此方法以泛型的格式返回,如果不存在泛型,则返回无泛型的形式。MethodTest的输出如下:

    在MethodTest是获取函数名为“printf”的函数,如果printf被重载了,则会通过for循环输出多个重载的函数;如果需要获取指定的函数,可以调用方法

getDeclaredMethod(String name, Class<?>... parameterTypes) ,根据指定的参数类型返回指定的函数。补充说明一下的是,Method.getGenericException可以返回泛型异常,不过这个方法很少会用到。 

    (2)调用Method方法

    java reflection为调用class对象的方法提供invoke函数,其方法签名如下:

Object invoke(Object obj, Object... args)

    第一个参数obj为调用方法对应的实例对象,如果调用的方法为静态的,obj应该设置为null;args为调用方法所需要的参数。下面演示一下如何调用参数固定的方法,调用参数不固定的方法,和如何调用静态方法。(参数不固定的方法,会将参数封装成一个数组)

    假设有如下一个类MyClass

public class MyClass {		public void print(String text) {		System.out.println(text);	}	public void printGreeting() {		System.out.println("how are you ?");	}	public double average(double[] values) {		double sum = 0.0;		for (double n : values) {			sum += n;		}		return sum/values.length;	}		public static int sum(int[] values) {		int sum = 0;		for (int i : values) {			sum += i;		}		return sum;	}}

    调用MyClass对象里的print()和printGreeting()方法(参数固定),如下:

MyClass object = new MyClass();Class
c = object.getClass();//调用有参数函数Method print = c.getDeclaredMethod("print", String.class);print.invoke(object, "invoke method successfully.");//调用无参数函数Method printGreeting = c.getDeclaredMethod("printGreeting");printGreeting.invoke(object);

    调用MyClass对象里的average()方法(参数不定),同时获取函数的返回值,如下:

//调用参数不固定的函数Method average = c.getDeclaredMethod("average", double[].class);double aveVal = (Double) average.invoke(object, new double[]{3.5,4.2,5.4,6.7});System.out.println("average : " + aveVal);

    调用MyClass的静态方法sum(),并获取其返回值,如下:

//调用静态函数Method staticMethod = c.getDeclaredMethod("sum", int[].class);int sum = (Integer)staticMethod.invoke(null, new int[]{1,3,5,7});System.out.println("sum :" + sum);

    所有调用的输出如下:

    有时候可能会遇到函数参数为泛型的情况,那又该如何调用呢??我们假设有这样泛型参数<T extends CharSequence>,方法如下:

public  print(T sequence){    System.out.println(sequence)}

    反射调用的代码如下:

Method print = clazz.getMethod(print, CharSequence.class);print.invoke(instance);

    (3)获取与解释Method的修饰符

    一个方法的可以包含的修饰符有:public, protected, private,static,final,abstract,synchronized,native,strictfp和注解。如需了解各种修饰符的作用,可以点击 。

    获取一个方法的修饰符非常简单,如上MyClass,我们回去printStatic()的修饰符:

Method staticMethod = c.getDeclaredMethod("sum", int[].class);System.out.println("修饰符:" + Modifier.toString(staticMethod.getModifiers()));

    其输出如下:修饰符: public static。

    顺带提一下:修饰符的输出是按一个顺序输出的:首先是public,protected或public,剩下的修饰符会按如下顺序输出:abstract,static,final,transient,volatile,synchronized,native,strictfp,interface。

   不过有时候仅仅是获取一个方法的修饰符是不够的,往往伴随着判断这个方法isSynthetic(由编译器生成),isVarArgs(参数数量是否可变),isBridge(是否是桥方法),桥方法指由编译器生成来支持泛型接口的方法。如下:

public class MethodModifierTest {		public static void main(String[] args) throws ClassNotFoundException {		Class
c = Class.forName("java.lang.Class"); Method[] allMethods = c.getDeclaredMethods(); for (Method m : allMethods) { if (!m.getName().equals("getConstructor")) { continue; } out.println("函数泛型字符窜表示:\n" + m.toGenericString()); out.println("修饰符: " + Modifier.toString(m.getModifiers())); out.println("synthetic?= " + m.isSynthetic() + "; 参数任意?: " + m.isVarArgs() + "; 桥方法?: " + m.isBridge()); } }}

输出如下:

    在此注意的是:在判断getConstructor()参数是否任意时,isVarArgs()返回true,说明其形式应该如下:

public Constructor
getConstructor(Class
... parameterTypes)

    而不是这样:

public Constructor
getConstructor(Class
[] parameterTypes)

    在MethodModifierTest,是会输出所有重载函数的,因为方法名相同的函数可以有多个(重载)。如,现在加载java.lang.Object,方法名为wait,改动如下

Class
c = Class.forName("java.lang.Object");if (!m.getName().equals("wait"))

    输出如下:(输出了wait的三种重载形式)

    再看如下的改动:

Class
c = Class.forName("java.lang.String");if (!m.getName().equals("compareTo"))

    其输出如下:

    在java.lang.String里只声明了“public int compareTo(String anotherString);”,可是却输出了另外一个由编译器生成的桥接方法。出现这种情况,是因为String实现了参数化接口Comparable。在类型擦除的状态下,Comparable.compareTo里的参数java.lang.Object会转化为java.lang.String。类型擦除后,java.lang.String不在与java.lang.Object匹配,也就没有重写Comparable.compareTo方法,而这是会引起编译错误的。而桥方法的使用就是为了防止这种编译错误的出现。

三、Constructor

    构造函数和Method基本一致,除了不包含返回值,构造函数也包括了修饰符,方法名,参数,注释符和异常。java.lang.reflect.Constructor提供各种API来获取相关信息,各个方法的使用和Method的类似,不在重复叙述。假设有一个类Person,如下:

public class Person {	private int id;	private String name;	public Person() {	}	public Person(int id) {		this.id = id;	}	public Person(int id, String name) {		this.id = id;		this.name = name;	}}

    如下代码可以找出包含某种类型(String)的参数的构造函数:

public static void main(String[] args) {        //构造函数所需要包含的参数类型        Class
cArg = String.class; Class
c = Person.class; Constructor[] allConstructors = c.getDeclaredConstructors(); for (Constructor ctor : allConstructors) { Class
[] pType = ctor.getParameterTypes(); for (int i = 0; i < pType.length; i++) { if (pType[i].equals(cArg)) { out.println("构造函数泛型表示:\n" + ctor.toGenericString()); //获取参数的泛型表示 Type[] gpType = ctor.getGenericParameterTypes(); for (int j = 0; j < gpType.length; j++) { char ch = (pType[j].equals(cArg) ? '*' : ' '); out.format("%7c%s[%d]: %s%n", ch, "GenericParameterType", j, gpType[j]); } break; } } } }

    其输出如下:

 

    接着演示一下如何通过反射获取指定构造函数来创建新的对象,如下:

public static void main(String[] args) throws Exception {        Class
c = Person.class; //获取默认构造函数 Constructor
c1 = c.getConstructor(); //获取参数为String的构造函数 Constructor
c2 = c.getConstructor(String.class); //获取参数为int,String的构造函数 Constructor
c3 = c.getConstructor(int.class,String.class); //使用反射构造新的Person对象 Person p1 = (Person) c1.newInstance(); Person p2 = (Person) c2.newInstance("张三"); Person p3 = (Person) c3.newInstance(1,"张三"); }

    注意的是,无参函数newInstance()要求对应的类必须提供无参构造函数。

 

   关于如何获取与解释构造函数的修饰符,其方法的使用与Method类似,一样可以判断构造函数isSynthetic(),isVarArgs()。在此不再叙述,详情可参考Method类的介绍。