Java反射Field类的使用全方位解析

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Java反射Field类的使⽤全⽅位解析
Field 提供有关类或接⼝的单个字段的信息,以及对它的动态访问权限。

反射的字段可能是⼀个类(静态)字段或实例字段。

Field 成员变量的介绍
每个成员变量有类型和值。

ng.reflect.Field 为我们提供了获取当前对象的成员变量的类型,和重新设值的⽅法。

获取变量的类型
类中的变量分为两种类型:基本类型和引⽤类型:
基本类型( 8 种)
整数:byte, short, int, long
浮点数:float, double
字符:char
布尔值:boolean
引⽤类型
所有的引⽤类型都继承⾃ ng.Object
类,枚举,数组,接⼝都是引⽤类型
java.io.Serializable 接⼝,基本类型的包装类(⽐如 ng.Double)也是引⽤类型
ng.reflect.Field 提供了两个⽅法获去变量的类型:
Field.getType():返回这个变量的类型
Field.getGenericType():如果当前属性有签名属性类型就返回,否则就返回 Field.getType()
Class<?> getType()
返回⼀个 Class 对象,它标识了此 Field 对象所表⽰字段的声明类型。

Type getGenericType()
返回⼀个 Type 对象,它表⽰此 Field 对象所表⽰字段的声明类型。

实例:
测试类:
public class A
{
public String id;
protected String name;
int age;
private String sex;
int[][] ints;
}
main⽅法:
public class Test
{
/**
* 返回该类所在包的包名字字符串
* @param thisClass ⼀个类的Class对象
* @return 该类的包名字字符串
*/
public static String getThisPackageName(Class<?> thisClass)
{
String thisClassName=thisClass.getName();
String thispackage=thisClassName.substring(0,stIndexOf("."));
return thispackage;
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException
{
Class strClass=Class.forName(getThisPackageName(Test.class)+".A");
//获取类的所有声明的字段
Field[] sField=strClass.getDeclaredFields();
for (Field field : 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:ng.String |GenericType:ng.String |
Field:name|Type:ng.String |GenericType:ng.String |
Field:age |Type:int |GenericType:int |
Field:sex |Type:ng.String |GenericType:ng.String |
Field:ints|Type:int[][] |GenericType:int[][] |
可以看到这个两个⽅法都能正确的返回当前字段的类型。

这两个⽅法的区别还是在返回类型上getType()⽅法返回的是Class<?>类型的,⽽getGenericType()返回的是Type类型的。

获取成员变量的修饰符
成员变量可以被以下修饰符修饰:
访问权限控制符:public, protected, private
限制只能有⼀个实例的:static
不允许修改的:final
不会被序列化:transient
线程共享数据的⼀致性:volatile
注解
类似获取 Class 的修饰符,我们可以使⽤ Field.getModifiers() ⽅法获取当前成员变量的修饰符。

返回 ng.reflect.Modifier 中定义的整形值。

然后使⽤ Modifier.toString(int mod)解码成字符串实例:获取上⾯的A类的字段的类型和修饰符:
public static void printField(Class<?> class1)
{
// 获取类的所有声明的字段
Field[] sField = class1.getDeclaredFields();
for (Field field : sField)
{
// 获取字段的名字
System.out.printf("字段:%-4s|", field.getName());
// 获取字段的类型的Class类,然后获取规范化的名字
System.out.printf("类型:%-18s|",field.getGenericType().getTypeName());
//使⽤Field.getModifiers(),可获取字段的修饰符编码,
//然后再使⽤Modifier.toString(int code),来解码成字字符串
System.out.printf("修饰符:%s", Modifier.toString(field.getModifiers()));
System.out.println();
}
}
main⽅法:
public static void main(String[] args) throws ClassNotFoundException,
NoSuchFieldException, SecurityException
{
Class AClass = Class.forName(getThisPackageName(Test.class) + ".A");
printField(AClass);
}
运⾏结果:
字段:id |类型:ng.String |修饰符:public
字段:name|类型:ng.String |修饰符:protected
字段:age |类型:int |修饰符:
字段:sex |类型:ng.String |修饰符:private
字段:ints|类型:int[][] |修饰符:
由于 Field 间接继承了 ng.reflect.AnnotatedElement ,因此运⾏时也可以获得修饰成员变量的注解,当然前提是这个注解被 ng.annotation.RetentionPolicy.RUNTIME 修饰。

获取和修改成员变量的值
拿到⼀个对象后,我们可以在运⾏时修改它的成员变量的值,对运⾏时来说,反射修改变量值的操作和类中修改变量的结果是⼀样的。

1.基本类型的获取⽅法:
byte getByte(Object obj)
获取⼀个静态或实例 byte 字段的值。

int getInt(Object obj)
获取 int 类型或另⼀个通过扩展转换可以转换为 int 类型的基本类型的静态或实例字段的值。

short getShort(Object obj)
获取 short 类型或另⼀个通过扩展转换可以转换为 short 类型的基本类型的静态或实例字段的值。

long getLong(Object obj)
获取 long 类型或另⼀个通过扩展转换可以转换为 long 类型的基本类型的静态或实例字段的值。

float getFloat(Object obj)
获取 float 类型或另⼀个通过扩展转换可以转换为 float 类型的基本类型的静态或实例字段的值。

double getDouble(Object obj)
获取 double 类型或另⼀个通过扩展转换可以转换为 double 类型的基本类型的静态或实例字段的值。

boolean getBoolean(Object obj)
获取⼀个静态或实例 boolean 字段的值。

char getChar(Object obj)
获取 char 类型或另⼀个通过扩展转换可以转换为 char 类型的基本类型的静态或实例字段的值。

2.基本类型的setter⽅法:
void setByte(Object obj, byte b)
将字段的值设置为指定对象上的⼀个 byte 值。

void setShort(Object obj, short s)
将字段的值设置为指定对象上的⼀个 short 值。

void setInt(Object obj, int i)
将字段的值设置为指定对象上的⼀个 int 值。

void setLong(Object obj, long l)
将字段的值设置为指定对象上的⼀个 long 值。

void setFloat(Object obj, float f)
将字段的值设置为指定对象上的⼀个 float 值。

void setDouble(Object obj, double d)
将字段的值设置为指定对象上的⼀个 double 值。

void setBoolean(Object obj, boolean z)
将字段的值设置为指定对象上的⼀个 boolean 值。

void setChar(Object obj, char c)
将字段的值设置为指定对象上的⼀个 char 值。

3.引⽤类型的getters⽅法:
Object get(Object obj)
返回指定对象上此 Field 表⽰的字段的值。

4.引⽤类型的setters⽅法:
void set(Object obj, Object value)
将指定对象变量上此 Field 对象表⽰的字段设置为指定的新值。

实例:
(1)基本类型的获取和修改:
测试类:
public class C
{
private int a;
private double d;
private boolean flag;
@Override
public String toString()
{
return "C [a=" + a + ", d=" + d + ", flag=" + flag + "]";
}
public C(int a, double d, boolean flag)
{
super();
this.a = a;
this.d = d;
this.flag = flag;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public double getD()
{
return d;
}
public void setD(double d)
{
this.d = d;
}
public boolean isFlag()
{
return flag;
}
public void setFlag(boolean flag)
{
this.flag = flag;
}
}
main⽅法类:
import ng.reflect.Field;
public class TestGetSetBase
{
public static void main(String[] args)
throws IllegalArgumentException, IllegalAccessException
{
C c = new C(10, 123.456, true);
System.out.println("c对象的值:");
System.out.println(c);
System.out.println("-------------------------------");
Class cClass = c.getClass();
// 获取所有的字段
Field[] cFields = cClass.getDeclaredFields();
for (Field field : cFields)
{
field.setAccessible(true);
System.out.println("获取到字段:" + field.getType().getCanonicalName() + ",值:" + field.get(c));
}
for (Field field : cFields)
{
if (field.getType().getCanonicalName() == "int")
{
field.setInt(c, 30);
} else if (field.getType().getCanonicalName() == "double")
{
field.setDouble(c, 6789.9901);
} else if (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)引⽤类型的获取和修改
测试类:
public class B
{
private String id;
private String Name;
public B(String id, String name)
{
super();
this.id = id;
Name = name;
}
public B(){}
@Override
public String toString()
{
return "B [id=" + id + ", Name=" + Name + "]";
}
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public String getName()
{
return Name;
}
public void setName(String name)
{
Name = name;
}
}
这⾥测试的类B的字段都是private类型的,在外部类是⽆法直接访问到这些成员属性的,想要获取和修改只能通过B类的getters和setters⽅法进⾏。

使⽤反射我可以绕过这些限制,因为是私有类型的要使⽤ Field.setAccessible(true); ⽅法解除限制main⽅法类:
import ng.reflect.Field;
public class TestGetSet
{
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException
{
B b=new B("B1000","⼩明");
System.out.println("b对象的值:");
System.out.println(b);
System.out.println("---------------------------------");
Class bClass=b.getClass();
//获取共有的字段列表
Field[] bFields=bClass.getDeclaredFields();
System.out.println("通过反射获取字段的值:");
//获取字段的值,Field.get(对象);
for (Field field : bFields)
{
field.setAccessible(true);//获取权限
//获取b对象的字段field⾥⾯的值
System.out.println("字段:"+field.getType().getName()+",值:"+field.get(b));
}
//修改字段的值
for (Field field : bFields)
{
field.set(b, "哈哈");
}
System.out.println("---------------------------------");
System.out.println("现在的b对象的值:");
System.out.println(b);
}
}
运⾏结果:
b对象的值:
B [id=B1000, Name=⼩明]
---------------------------------
通过反射获取字段的值:
字段:ng.String,值:B1000
字段:ng.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的⽅法:
boolean equals(Object obj)
将此 Field 与指定对象⽐较。

boolean isEnumConstant()
如果此字段表⽰枚举类型的元素,则返回 true;否则返回 false。

boolean isSynthetic()
如果此字段是复合字段,则返回 true;否则返回 false。

返回String的⽅法:
String getName()
返回此 Field 对象表⽰的字段的名称。

String toGenericString()
返回⼀个描述此 Field(包括其⼀般类型)的字符串。

String toString()
返回⼀个描述此 Field 的字符串。

其他⽅法:
1.equals()和hashCode()
int hashCode()
返回该 Field 的哈希码。

boolean equals(Object obj)
将此 Field 与指定对象⽐较。

2.返回注释的⽅法:
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。

Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注释。

3.返回字段所在的类或者接⼝的Class对象
Class<?> getDeclaringClass()
返回表⽰类或接⼝的 Class 对象,该类或接⼝声明由此 Field 对象表⽰的字段。

4.返回字段的类型(Type)
Type getGenericType()
返回⼀个 Type 对象,它表⽰此 Field 对象所表⽰字段的声明类型。

5.返回修饰符编码:这个⽅法上⾯已经提到了,可以使⽤Modifier.toString(int mod)⽅法,把获取到的编码转换成修饰符字符串 int getModifiers()
以整数形式返回由此 Field 对象表⽰的字段的 Java 语⾔修饰符。

常见错误 1 :⽆法转换类型导致的 ng.IllegalArgumentException
在使⽤反射获取或者修改⼀个变量的值时,编译器不会进⾏⾃动装/拆箱。

所以我们⽆法给 Integer 类型的属性使⽤ setInt() ⽅法重新设值,必须给它赋⼀个 Integer 对象才可以。

否则会因为⽆法转换类型⽽出现ng.IllegalArgumentException
实例:
import ng.reflect.Field;
public class TestInterger
{
private Integer integer;
public TestInterger(Integer integer)
{
this.integer = integer;
}
public String toString()
{
return "TestInterger [integer=" + integer + "]";
}
public static void main(String[] args) throws NoSuchFieldException,
SecurityException, IllegalArgumentException, IllegalAccessException
{
// 这⾥传⼊的30是int类型的,会⾃动装箱成Integer类型
TestInterger testInterger = new TestInterger(30);
System.out.println("testInteger对象:");
System.out.println(testInterger);
System.out.println("----------------------------------");
Class thisClass = testInterger.getClass();
// 获取integer字段
Field inField = 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]
----------------------------------
成员属性:ng.Integer,值:30
Exception in thread "main" ng.IllegalArgumentException:
Can not set ng.Integer field reflect.fieldtest.type.TestInterger.integer to (int)90
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
at sun.reflect.UnsafeObjectFieldAccessorImpl.setInt(Unknown Source)
at ng.reflect.Field.setInt(Unknown Source)
at reflect.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, new Integer(90))
常见错误 2:反射⾮ public 的变量导致的 NoSuchFieldException
如果你使⽤ Class.getField() 或者 Class.getFields() 获取⾮ public 的变量,编译器会报 ng.NoSuchFieldException 错。

常见错误 3 :修改 final类型的变量导致的 IllegalAccessException
当你想要获取或者修改不可修改(final)的变量时,会导致IllegalAccessException。

由于 Field 继承⾃ AccessibleObject , 我们可以使⽤ AccessibleObject.setAccessible() ⽅法告诉安全机制,这个变量可以访问。

也就是Field.setAccessible(true)。

告诉安全机制当前的这字段不做访问权限检查,这样我们就能反射修改final修饰成常量了。

以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。

如有错误或未考虑完全的地⽅,望不吝赐教。

相关文档
最新文档