Java反射的Field类如何使用
这篇文章主要介绍了Java反射的Field类如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java反射的Field类如何使用文章都会有所收获,下面我们一起来看看吧。
Field 成员变量的介绍每个成员变量有类型和值。
java.lang.reflect.Field 为我们提供了获取当前对象的成员变量的类型,和重新设值的方法。
获取变量的类型类中的变量分为两种类型:基本类型和引用类型:
基本类型( 8 种)
整数:byte, short, int, long
浮点数:float, double
字符:char
布尔值:boolean
引用类型
所有的引用类型都继承自 java.lang.Object
类,枚举,数组,接口都是引用类型
java.io.Serializable 接口,基本类型的包装类(比如 java.lang.Double)也是引用类型
java.lang.reflect.Field 提供了两个方法获去变量的类型:
Field.getType():返回这个变量的类型
Field.getGenericType():如果当前属性有签名属性类型就返回,否则就返回 Field.getType()
实例:
Class<?>getType()返回一个Class对象,它标识了此Field对象所表示字段的声明类型。TypegetGenericType()返回一个Type对象,它表示此Field对象所表示字段的声明类型。
测试类:
publicclassA{publicStringid;protectedStringname;intage;privateStringsex;int[][]ints;}
main方法:
publicclassTest{/***返回该类所在包的包名字字符串*@paramthisClass一个类的Class对象*@return该类的包名字字符串*/publicstaticStringgetThisPackageName(Class<?>thisClass){StringthisClassName=thisClass.getName();Stringthispackage=thisClassName.substring(0,thisClassName.lastIndexOf("."));returnthispackage;}publicstaticvoidmain(String[]args)throwsClassNotFoundException,NoSuchFieldException,SecurityException{ClassstrClass=Class.forName(getThisPackageName(Test.class)+".A");//获取类的所有声明的字段Field[]sField=strClass.getDeclaredFields();for(Fieldfield:sField){//获取字段的名字System.out.printf("Field:%-4s|",field.getName());//获取字段的类型的Class类,然后获取规范化的名字System.out.printf("Type:%-18s|",field.getType().getCanonicalName());//获取字段的类型的Type类对象,然后获取类的名字System.out.printf("GenericType:%-18s|",field.getGenericType().getTypeName());System.out.println();}}}
运行结果:
Field:id|Type:java.lang.String|GenericType:java.lang.String|Field:name|Type:java.lang.String|GenericType:java.lang.String|Field:age|Type:int|GenericType:int|Field:sex|Type:java.lang.String|GenericType:java.lang.String|Field:ints|Type:int[][]|GenericType:int[][]|
可以看到这个两个方法都能正确的返回当前字段的类型。这两个方法的区别还是在返回类型上
getType()方法返回的是Class<?>类型的,而getGenericType()返回的是Type类型的。
获取成员变量的修饰符成员变量可以被以下修饰符修饰:
访问权限控制符:public, protected, private
限制只能有一个实例的:static
不允许修改的:final
不会被序列化:transient
线程共享数据的一致性:volatile
注解
类似获取 Class 的修饰符,我们可以使用 Field.getModifiers() 方法获取当前成员变量的修饰符。
返回 java.lang.reflect.Modifier 中定义的整形值。然后使用 Modifier.toString(int mod)解码成字符串
实例:获取上面的A类的字段的类型和修饰符:
publicstaticvoidprintField(Class<?>class1){//获取类的所有声明的字段Field[]sField=class1.getDeclaredFields();for(Fieldfield:sField){//获取字段的名字System.out.printf("字段:%-4s|",field.getName());//获取字段的类型的Class类,然后获取规范化的名字System.out.printf("类型:%-18s|",field.getGenericType().getTypeName());//使用Field.getModifiers(),可获取字段的修饰符编码,//然后再使用Modifier.toString(intcode),来解码成字字符串System.out.printf("修饰符:%s",Modifier.toString(field.getModifiers()));System.out.println();}}
main方法:
publicstaticvoidmain(String[]args)throwsClassNotFoundException,NoSuchFieldException,SecurityException{ClassAClass=Class.forName(getThisPackageName(Test.class)+".A");printField(AClass);}
运行结果:
字段:id|类型:java.lang.String|修饰符:public字段:name|类型:java.lang.String|修饰符:protected字段:age|类型:int|修饰符:字段:sex|类型:java.lang.String|修饰符:private字段:ints|类型:int[][]|修饰符:
由于 Field 间接继承了 java.lang.reflect.AnnotatedElement ,因此运行时也可以获得修饰成员变量的注解,当然前提是这个注解被 java.lang.annotation.RetentionPolicy.RUNTIME 修饰。
获取和修改成员变量的值拿到一个对象后,我们可以在运行时修改它的成员变量的值,对运行时来说,反射修改变量值的操作和类中修改变量的结果是一样的。
1.基本类型的获取方法:bytegetByte(Objectobj)获取一个静态或实例byte字段的值。intgetInt(Objectobj)获取int类型或另一个通过扩展转换可以转换为int类型的基本类型的静态或实例字段的值。shortgetShort(Objectobj)获取short类型或另一个通过扩展转换可以转换为short类型的基本类型的静态或实例字段的值。longgetLong(Objectobj)获取long类型或另一个通过扩展转换可以转换为long类型的基本类型的静态或实例字段的值。floatgetFloat(Objectobj)获取float类型或另一个通过扩展转换可以转换为float类型的基本类型的静态或实例字段的值。doublegetDouble(Objectobj)获取double类型或另一个通过扩展转换可以转换为double类型的基本类型的静态或实例字段的值。booleangetBoolean(Objectobj)获取一个静态或实例boolean字段的值。chargetChar(Objectobj)获取char类型或另一个通过扩展转换可以转换为char类型的基本类型的静态或实例字段的值。2.基本类型的setter方法:
voidsetByte(Objectobj,byteb)将字段的值设置为指定对象上的一个byte值。voidsetShort(Objectobj,shorts)将字段的值设置为指定对象上的一个short值。voidsetInt(Objectobj,inti)将字段的值设置为指定对象上的一个int值。voidsetLong(Objectobj,longl)将字段的值设置为指定对象上的一个long值。voidsetFloat(Objectobj,floatf)将字段的值设置为指定对象上的一个float值。voidsetDouble(Objectobj,doubled)将字段的值设置为指定对象上的一个double值。voidsetBoolean(Objectobj,booleanz)将字段的值设置为指定对象上的一个boolean值。voidsetChar(Objectobj,charc)将字段的值设置为指定对象上的一个char值。3.引用类型的getters方法:
Objectget(Objectobj)
返回指定对象上此 Field 表示的字段的值。
4.引用类型的setters方法:voidset(Objectobj,Objectvalue)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
实例:
(1)基本类型的获取和修改:
测试类:
publicclassC{privateinta;privatedoubled;privatebooleanflag;@OverridepublicStringtoString(){return"C[a="+a+",d="+d+",flag="+flag+"]";}publicC(inta,doubled,booleanflag){super();this.a=a;this.d=d;this.flag=flag;}publicintgetA(){returna;}publicvoidsetA(inta){this.a=a;}publicdoublegetD(){returnd;}publicvoidsetD(doubled){this.d=d;}publicbooleanisFlag(){returnflag;}publicvoidsetFlag(booleanflag){this.flag=flag;}}
main方法类:
importjava.lang.reflect.Field;publicclassTestGetSetBase{publicstaticvoidmain(String[]args)throwsIllegalArgumentException,IllegalAccessException{Cc=newC(10,123.456,true);System.out.println("c对象的值:");System.out.println(c);System.out.println("-------------------------------");ClasscClass=c.getClass();//获取所有的字段Field[]cFields=cClass.getDeclaredFields();for(Fieldfield:cFields){field.setAccessible(true);System.out.println("获取到字段:"+field.getType().getCanonicalName()+",值:"+field.get(c));}for(Fieldfield:cFields){if(field.getType().getCanonicalName()=="int"){field.setInt(c,30);}elseif(field.getType().getCanonicalName()=="double"){field.setDouble(c,6789.9901);}elseif(field.getType().getCanonicalName()=="boolean"){field.setBoolean(c,false);}//System.out.println(field.getType().getCanonicalName());}System.out.println("-------------------------------");System.out.println("现在的c对象的值:");System.out.println(c);}}
运行结果:
c对象的值:C[a=10,d=123.456,flag=true]-------------------------------获取到字段:int,值:10获取到字段:double,值:123.456获取到字段:boolean,值:true-------------------------------现在的c对象的值:C[a=30,d=6789.9901,flag=false](2)引用类型的获取和修改
测试类:
publicclassB{privateStringid;privateStringName;publicB(Stringid,Stringname){super();this.id=id;Name=name;}publicB(){}@OverridepublicStringtoString(){return"B[id="+id+",Name="+Name+"]";}publicStringgetId(){returnid;}publicvoidsetId(Stringid){this.id=id;}publicStringgetName(){returnName;}publicvoidsetName(Stringname){Name=name;}}
这里测试的类B的字段都是private类型的,在外部类是无法直接访问到这些成员属性的,想要获取和修改只能通过B类的getters和setters方法进行。使用反射我可以绕过这些限制,因为是私有类型的要使用 Field.setAccessible(true); 方法解除限制
main方法类:
importjava.lang.reflect.Field;publicclassTestGetSet{publicstaticvoidmain(String[]args)throwsIllegalArgumentException,IllegalAccessException{Bb=newB("B1000","小明");System.out.println("b对象的值:");System.out.println(b);System.out.println("---------------------------------");ClassbClass=b.getClass();//获取共有的字段列表Field[]bFields=bClass.getDeclaredFields();System.out.println("通过反射获取字段的值:");//获取字段的值,Field.get(对象);for(Fieldfield:bFields){field.setAccessible(true);//获取权限//获取b对象的字段field里面的值System.out.println("字段:"+field.getType().getName()+",值:"+field.get(b));}//修改字段的值for(Fieldfield:bFields){field.set(b,"哈哈");}System.out.println("---------------------------------");System.out.println("现在的b对象的值:");System.out.println(b);}}
运行结果:
b对象的值:B[id=B1000,Name=小明]---------------------------------通过反射获取字段的值:字段:java.lang.String,值:B1000字段:java.lang.String,值:小明---------------------------------现在的b对象的值:B[id=哈哈,Name=哈哈]
再说一下setAccessible()方法,Field的setAccessible()方法是从AccessibleObject类继承而来的。AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。
它提供了在使用时 取消默认 Java 语言访问控制检查的能力。
一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在Field,Method和Constructor这些类上调用setAccessible()方法,我们可以操作这些字段无法访问的字段。
返回boolean的方法:
booleanequals(Objectobj)将此Field与指定对象比较。booleanisEnumConstant()如果此字段表示枚举类型的元素,则返回true;否则返回false。booleanisSynthetic()如果此字段是复合字段,则返回true;否则返回false。
返回String的方法:
StringgetName()返回此Field对象表示的字段的名称。StringtoGenericString()返回一个描述此Field(包括其一般类型)的字符串。StringtoString()返回一个描述此Field的字符串。其他方法:
1.equals()和hashCode()
inthashCode()返回该Field的哈希码。booleanequals(Objectobj)将此Field与指定对象比较。
2.返回注释的方法:
<TextendsAnnotation>TgetAnnotation(Class<T>annotationClass)如果存在该元素的指定类型的注释,则返回这些注释,否则返回null。Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注释。
3.返回字段所在的类或者接口的Class对象
Class<?>getDeclaringClass()返回表示类或接口的Class对象,该类或接口声明由此Field对象表示的字段。
4.返回字段的类型(Type)
TypegetGenericType()返回一个Type对象,它表示此Field对象所表示字段的声明类型。
5.返回修饰符编码:这个方法上面已经提到了,可以使用Modifier.toString(int mod)方法,把获取到的编码转换成修饰符字符串
intgetModifiers()以整数形式返回由此Field对象表示的字段的Java语言修饰符。常见错误 1 :无法转换类型导致的 java.lang.IllegalArgumentException
在使用反射获取或者修改一个变量的值时,编译器不会进行自动装/拆箱。所以我们无法给 Integer 类型的属性使用 setInt() 方法重新设值,必须给它赋一个 Integer 对象才可以。
否则会因为无法转换类型而出现java.lang.IllegalArgumentException
实例:
importjava.lang.reflect.Field;publicclassTestInterger{privateIntegerinteger;publicTestInterger(Integerinteger){this.integer=integer;}publicStringtoString(){return"TestInterger[integer="+integer+"]";}publicstaticvoidmain(String[]args)throwsNoSuchFieldException,SecurityException,IllegalArgumentException,IllegalAccessException{//这里传入的30是int类型的,会自动装箱成Integer类型TestIntergertestInterger=newTestInterger(30);System.out.println("testInteger对象:");System.out.println(testInterger);System.out.println("----------------------------------");ClassthisClass=testInterger.getClass();//获取integer字段FieldinField=thisClass.getDeclaredField("integer");inField.setAccessible(true);//不做访问控制检查//获取字段的值System.out.println("成员属性:"+inField.getType().getCanonicalName()+",值:"+inField.get(testInterger));//修改成员属性integer:inField.setInt(testInterger,90);System.out.println("----------------------------------");System.out.println(testInterger);}}
运行结果:
testInteger对象:TestInterger[integer=30]----------------------------------成员属性:java.lang.Integer,值:30Exceptioninthread"main"java.lang.IllegalArgumentException:Cannotsetjava.lang.Integerfieldreflect.fieldtest.type.TestInterger.integerto(int)90atsun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnknownSource)atsun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnknownSource)atsun.reflect.UnsafeObjectFieldAccessorImpl.setInt(UnknownSource)atjava.lang.reflect.Field.setInt(UnknownSource)atreflect.fieldtest.type.TestInterger.main(TestInterger.java:34)
解决方法:把上面的 inField.setInt(testInterger, 90);改成 inField.set(testInterger, 90);即可;
set方法原型:
Field.set(Objet obj2,Object obj2),这样我们传入90这个int类型的数据时,在编译阶段编译器会自动进行装箱等同于
inField.set(testInterger, new Integer(90))。这样运行时,获取到的是Integer类型的,能正常的传入。
这里再来说一下自动拆箱装箱的事情:
自动装箱是java编译器在java原生类型和对应的对象包装类型上做的自动转换。
例如,把int 装换成 Integer double转换成Double等等。
如果是反过来转换,那么叫做自动拆箱,也是编译器为我们做的事情。
强调:自动拆箱装箱发生在编译时刻,反射时发生在程序运行时刻。
为了不混淆,利用反射修改包装类的值的时候,使用set方法,并且尽量手动装箱,也就是写成下面的形式:
inField.set(testInterger,newInteger(90))常见错误 2:反射非 public 的变量导致的 NoSuchFieldException
如果你使用 Class.getField() 或者 Class.getFields() 获取非 public 的变量,编译器会报 java.lang.NoSuchFieldException 错。
常见错误 3 :修改 final类型的变量导致的 IllegalAccessException当你想要获取或者修改 不可修改(final)的变量时,会导致IllegalAccessException。
由于 Field 继承自 AccessibleObject , 我们可以使用 AccessibleObject.setAccessible() 方法告诉安全机制,这个变量可以访问。
也就是Field.setAccessible(true)。告诉安全机制当前的这字段不做访问权限检查,这样我们就能反射修改final修饰成常量了。
关于“Java反射的Field类如何使用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“Java反射的Field类如何使用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注亿速云行业资讯频道。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。