java method field_Field,Method,Constructor— java 7 Reflection(三)

承接前篇对java Reflection的介绍 :

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

获取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类型。以下是部分输出:

0ddbd887b50f91183a5cb7824a2e3302.png8bb744c486e46fc1beb9cf53fb919841.png

62ae9441832991048fe880cb324a8b15.pngfcf5ef40e86228e648f5de5075641b49.png

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

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

(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));

}

}

其输出如下:

aa4284094dc70c129b7f9e9e11ef0423.png

细心的你,也许会发现,在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和注解,需要了解修饰符更多的信息,可以参考java修饰符概述。

可以使用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;

}

}

其输出如下:

3818e2c40e7743a39dd463d38f5af89e.png

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

Class> c = FieldModifier.class;

//指定修饰符

String[] midifiers = { "private" };

输出如下:

e0c3d341814db32a2d5eac7a733c7f87.png

类似的有:

61d35d628e92d97c28ae57788dbed9b3.png

a634fbed16a0f93168bfd2942e138e4b.png

再看如下修改:

Class> c = Gender.class;

//指定修饰符

String[] midifiers = { "private","static","finale" };

其输出如下:

74ad314aed8c3fdf6352bc34f790ea6a.png

发现输出了一些并没有在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的输出如下:

dbe8951a5e6011025809b6b1c4682ac1.png

在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);

所有调用的输出如下:

c5099a533e1821dd353a97d2480d73c6.png

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

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());

}

}

}

输出如下:

683dfeed925db9e0b1ea5cd2c407ea1a.png

在此注意的是:在判断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的三种重载形式)

81193217b9061b350e3c46da1baa5e2d.png

再看如下的改动:

Class> c = Class.forName("java.lang.String");

if (!m.getName().equals("compareTo"))

其输出如下:

c33f5d9c1c78fb874b69da1ac919b755.png

在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;

}

}

}

}

其输出如下:

16704794ce1a6f7c1b2b32532403897d.png

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

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类的介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值