在 Java 語(yǔ)言中的反射機(jī)制非常之重要,作為反射可用方法之一的 Field 類(lèi)提供有關(guān)類(lèi)或接口的單個(gè)字段的信息,以及對(duì)它的動(dòng)態(tài)訪(fǎng)問(wèn)權(quán)限。反射的字段可能是一個(gè)類(lèi)(靜態(tài))字段或?qū)嵗侄巍1疚膶⒑痛蠹曳窒硪幌?Java 反射機(jī)制中的 Field 類(lèi)和具體使用方法。
Field 成員變量的介紹
每個(gè)成員變量有類(lèi)型和值。
java.lang.reflect.Field 為我們提供了獲取當(dāng)前對(duì)象的成員變量的類(lèi)型,和重新設(shè)值的方法。
獲取變量的類(lèi)型
類(lèi)中的變量分為兩種類(lèi)型:基本類(lèi)型和引用類(lèi)型:
基本類(lèi)型( 8 種)
整數(shù):byte, short, int, long
浮點(diǎn)數(shù):float, double
字符:char
布爾值:boolean
引用類(lèi)型
所有的引用類(lèi)型都繼承自 java.lang.Object
類(lèi),枚舉,數(shù)組,接口都是引用類(lèi)型
java.io.Serializable 接口,基本類(lèi)型的包裝類(lèi)(比如 java.lang.Double)也是引用類(lèi)型
java.lang.reflect.Field 提供了兩個(gè)方法獲去變量的類(lèi)型:
Field.getType():返回這個(gè)變量的類(lèi)型
Field.getGenericType():如果當(dāng)前屬性有簽名屬性類(lèi)型就返回,否則就返回 Field.getType()
實(shí)例:
Class<?> getType()
返回一個(gè) Class 對(duì)象,它標(biāo)識(shí)了此 Field 對(duì)象所表示字段的聲明類(lèi)型。
Type getGenericType()
返回一個(gè) Type 對(duì)象,它表示此 Field 對(duì)象所表示字段的聲明類(lèi)型。
測(cè)試類(lèi):
public class A
{
public String id;
protected String name;
int age;
private String sex;
int[][] ints;
}
main方法:
public class Test
{
/**
* 返回該類(lèi)所在包的包名字字符串
* @param thisClass 一個(gè)類(lèi)的Class對(duì)象
* @return 該類(lèi)的包名字字符串
*/
public static String getThisPackageName(Class<?> thisClass)
{
String thisClassName=thisClass.getName();
String thispackage=thisClassName.substring(0,thisClassName.lastIndexOf("."));
return thispackage;
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException
{
Class strClass=Class.forName(getThisPackageName(Test.class)+".A");
//獲取類(lèi)的所有聲明的字段
Field[] sField=strClass.getDeclaredFields();
for (Field field : sField)
{
//獲取字段的名字
System.out.printf("Field:%-4s|",field.getName());
//獲取字段的類(lèi)型的Class類(lèi),然后獲取規(guī)范化的名字
System.out.printf("Type:%-18s|",field.getType().getCanonicalName());
//獲取字段的類(lèi)型的Type類(lèi)對(duì)象,然后獲取類(lèi)的名字
System.out.printf("GenericType:%-18s|",field.getGenericType().getTypeName());
System.out.println();
}
}
}
運(yùn)行結(jié)果:
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[][] |
可以看到這個(gè)兩個(gè)方法都能正確的返回當(dāng)前字段的類(lèi)型。這兩個(gè)方法的區(qū)別還是在返回類(lèi)型上
getType()方法返回的是Class<?>類(lèi)型的,而getGenericType()返回的是Type類(lèi)型的。
獲取成員變量的修飾符
成員變量可以被以下修飾符修飾:
訪(fǎng)問(wèn)權(quán)限控制符:public, protected, private
限制只能有一個(gè)實(shí)例的:static
不允許修改的:final
不會(huì)被序列化:transient
線(xiàn)程共享數(shù)據(jù)的一致性:volatile
注解
類(lèi)似獲取 Class 的修飾符,我們可以使用 Field.getModifiers() 方法獲取當(dāng)前成員變量的修飾符。
返回 java.lang.reflect.Modifier 中定義的整形值。然后使用 Modifier.toString(int mod)解碼成字符串
實(shí)例:獲取上面的A類(lèi)的字段的類(lèi)型和修飾符:
public static void printField(Class<?> class1)
{
// 獲取類(lèi)的所有聲明的字段
Field[] sField = class1.getDeclaredFields();
for (Field field : sField)
{
// 獲取字段的名字
System.out.printf("字段:%-4s|", field.getName());
// 獲取字段的類(lèi)型的Class類(lèi),然后獲取規(guī)范化的名字
System.out.printf("類(lèi)型:%-18s|",field.getGenericType().getTypeName());
//使用Field.getModifiers(),可獲取字段的修飾符編碼,
//然后再使用Modifier.toString(int code),來(lái)解碼成字字符串
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);
}
運(yùn)行結(jié)果:
字段:id |類(lèi)型:java.lang.String |修飾符:public
字段:name|類(lèi)型:java.lang.String |修飾符:protected
字段:age |類(lèi)型:int |修飾符:
字段:sex |類(lèi)型:java.lang.String |修飾符:private
字段:ints|類(lèi)型:int[][] |修飾符:
由于 Field 間接繼承了 java.lang.reflect.AnnotatedElement ,因此運(yùn)行時(shí)也可以獲得修飾成員變量的注解,當(dāng)然前提是這個(gè)注解被 java.lang.annotation.RetentionPolicy.RUNTIME 修飾。
獲取和修改成員變量的值
拿到一個(gè)對(duì)象后,我們可以在運(yùn)行時(shí)修改它的成員變量的值,對(duì)運(yùn)行時(shí)來(lái)說(shuō),反射修改變量值的操作和類(lèi)中修改變量的結(jié)果是一樣的。
1.基本類(lèi)型的獲取方法:
byte getByte(Object obj)
獲取一個(gè)靜態(tài)或?qū)嵗?byte 字段的值。
int getInt(Object obj)
獲取 int 類(lèi)型或另一個(gè)通過(guò)擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 int 類(lèi)型的基本類(lèi)型的靜態(tài)或?qū)嵗侄蔚闹怠?
short getShort(Object obj)
獲取 short 類(lèi)型或另一個(gè)通過(guò)擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 short 類(lèi)型的基本類(lèi)型的靜態(tài)或?qū)嵗侄蔚闹怠?
long getLong(Object obj)
獲取 long 類(lèi)型或另一個(gè)通過(guò)擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 long 類(lèi)型的基本類(lèi)型的靜態(tài)或?qū)嵗侄蔚闹怠?
float getFloat(Object obj)
獲取 float 類(lèi)型或另一個(gè)通過(guò)擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 float 類(lèi)型的基本類(lèi)型的靜態(tài)或?qū)嵗侄蔚闹怠?
double getDouble(Object obj)
獲取 double 類(lèi)型或另一個(gè)通過(guò)擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 double 類(lèi)型的基本類(lèi)型的靜態(tài)或?qū)嵗侄蔚闹怠?
boolean getBoolean(Object obj)
獲取一個(gè)靜態(tài)或?qū)嵗?boolean 字段的值。
char getChar(Object obj)
獲取 char 類(lèi)型或另一個(gè)通過(guò)擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 char 類(lèi)型的基本類(lèi)型的靜態(tài)或?qū)嵗侄蔚闹怠?/code>
2.基本類(lèi)型的setter方法:
void setByte(Object obj, byte b)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) byte 值。
void setShort(Object obj, short s)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) short 值。
void setInt(Object obj, int i)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) int 值。
void setLong(Object obj, long l)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) long 值。
void setFloat(Object obj, float f)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) float 值。
void setDouble(Object obj, double d)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) double 值。
void setBoolean(Object obj, boolean z)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) boolean 值。
void setChar(Object obj, char c)
將字段的值設(shè)置為指定對(duì)象上的一個(gè) char 值。
3.引用類(lèi)型的getters方法:
Object get(Object obj)
返回指定對(duì)象上此 Field 表示的字段的值。
4.引用類(lèi)型的setters方法:
void set(Object obj, Object value)
將指定對(duì)象變量上此 Field 對(duì)象表示的字段設(shè)置為指定的新值。
實(shí)例:
(1)基本類(lèi)型的獲取和修改:
測(cè)試類(lèi):
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方法類(lèi):
import java.lang.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對(duì)象的值:");
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("現(xiàn)在的c對(duì)象的值:");
System.out.println(c);
}
}
運(yùn)行結(jié)果:
c對(duì)象的值:
C [a=10, d=123.456, flag=true]
-------------------------------
獲取到字段:int,值:10
獲取到字段:double,值:123.456
獲取到字段:boolean,值:true
-------------------------------
現(xiàn)在的c對(duì)象的值:
C [a=30, d=6789.9901, flag=false]
(2)引用類(lèi)型的獲取和修改
測(cè)試類(lèi):
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;
}
}
這里測(cè)試的類(lèi)B的字段都是private類(lèi)型的,在外部類(lèi)是無(wú)法直接訪(fǎng)問(wèn)到這些成員屬性的,想要獲取和修改只能通過(guò)B類(lèi)的getters和setters方法進(jìn)行。使用反射我可以繞過(guò)這些限制,因?yàn)槭撬接蓄?lèi)型的要使用 Field.setAccessible(true); 方法解除限制
main方法類(lèi):
import java.lang.reflect.Field;
public class TestGetSet
{
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException
{
B b=new B("B1000","小明");
System.out.println("b對(duì)象的值:");
System.out.println(b);
System.out.println("---------------------------------");
Class bClass=b.getClass();
//獲取共有的字段列表
Field[] bFields=bClass.getDeclaredFields();
System.out.println("通過(guò)反射獲取字段的值:");
//獲取字段的值,F(xiàn)ield.get(對(duì)象);
for (Field field : bFields)
{
field.setAccessible(true);//獲取權(quán)限
//獲取b對(duì)象的字段field里面的值
System.out.println("字段:"+field.getType().getName()+",值:"+field.get(b));
}
//修改字段的值
for (Field field : bFields)
{
field.set(b, "哈哈");
}
System.out.println("---------------------------------");
System.out.println("現(xiàn)在的b對(duì)象的值:");
System.out.println(b);
}
}
運(yùn)行結(jié)果:
b對(duì)象的值: B [id=B1000, Name=小明] --------------------------------- 通過(guò)反射獲取字段的值: 字段:java.lang.String,值:B1000 字段:java.lang.String,值:小明 --------------------------------- 現(xiàn)在的b對(duì)象的值: B [id=哈哈, Name=哈哈]
再說(shuō)一下setAccessible()方法,F(xiàn)ield的setAccessible()方法是從AccessibleObject類(lèi)繼承而來(lái)的。AccessibleObject 類(lèi)是 Field、Method 和 Constructor 對(duì)象的基類(lèi)。
它提供了在使用時(shí) 取消默認(rèn) Java 語(yǔ)言訪(fǎng)問(wèn)控制檢查的能力。
一般情況下,我們并不能對(duì)類(lèi)的私有字段進(jìn)行操作,利用反射也不例外,但有的時(shí)候,例如要序列化的時(shí)候,我們又必須有能力去處理這些字段,這時(shí)候,我們就需要調(diào)用AccessibleObject上的setAccessible()方法來(lái)允許這種訪(fǎng)問(wèn),而由于反射類(lèi)中的Field,Method和Constructor繼承自AccessibleObject,因此,通過(guò)在Field,Method和Constructor這些類(lèi)上調(diào)用setAccessible()方法,我們可以操作這些字段無(wú)法訪(fǎng)問(wèn)的字段。
返回boolean的方法:
boolean equals(Object obj)
將此 Field 與指定對(duì)象比較。
boolean isEnumConstant()
如果此字段表示枚舉類(lèi)型的元素,則返回 true;否則返回 false。
boolean isSynthetic()
如果此字段是復(fù)合字段,則返回 true;否則返回 false。
返回String的方法:
String getName()
返回此 Field 對(duì)象表示的字段的名稱(chēng)。
String toGenericString()
返回一個(gè)描述此 Field(包括其一般類(lèi)型)的字符串。
String toString()
返回一個(gè)描述此 Field 的字符串。
其他方法:
1.equals()和hashCode()
int hashCode()
返回該 Field 的哈希碼。
boolean equals(Object obj)
將此 Field 與指定對(duì)象比較。
2.返回注釋的方法:
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
如果存在該元素的指定類(lèi)型的注釋?zhuān)瑒t返回這些注釋?zhuān)駝t返回 null。
Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注釋。
3.返回字段所在的類(lèi)或者接口的Class對(duì)象
Class<?> getDeclaringClass()
返回表示類(lèi)或接口的 Class 對(duì)象,該類(lèi)或接口聲明由此 Field 對(duì)象表示的字段。
4.返回字段的類(lèi)型(Type)
Type getGenericType()
返回一個(gè) Type 對(duì)象,它表示此 Field 對(duì)象所表示字段的聲明類(lèi)型。
5.返回修飾符編碼:這個(gè)方法上面已經(jīng)提到了,可以使用Modifier.toString(int mod)方法,把獲取到的編碼轉(zhuǎn)換成修飾符字符串
int getModifiers()
以整數(shù)形式返回由此 Field 對(duì)象表示的字段的 Java 語(yǔ)言修飾符。
常見(jiàn)錯(cuò)誤 1 :無(wú)法轉(zhuǎn)換類(lèi)型導(dǎo)致的 java.lang.IllegalArgumentException
在使用反射獲取或者修改一個(gè)變量的值時(shí),編譯器不會(huì)進(jìn)行自動(dòng)裝/拆箱。所以我們無(wú)法給 Integer 類(lèi)型的屬性使用 setInt() 方法重新設(shè)值,必須給它賦一個(gè) Integer 對(duì)象才可以。
否則會(huì)因?yàn)闊o(wú)法轉(zhuǎn)換類(lèi)型而出現(xiàn)java.lang.IllegalArgumentException
實(shí)例:
import java.lang.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類(lèi)型的,會(huì)自動(dòng)裝箱成Integer類(lèi)型
TestInterger testInterger = new TestInterger(30);
System.out.println("testInteger對(duì)象:");
System.out.println(testInterger);
System.out.println("----------------------------------");
Class thisClass = testInterger.getClass();
// 獲取integer字段
Field inField = thisClass.getDeclaredField("integer");
inField.setAccessible(true);// 不做訪(fǎng)問(wèn)控制檢查
// 獲取字段的值
System.out.println("成員屬性:" + inField.getType().getCanonicalName()
+ ",值:" + inField.get(testInterger));
// 修改成員屬性integer:
inField.setInt(testInterger, 90);
System.out.println("----------------------------------");
System.out.println(testInterger);
}
}
運(yùn)行結(jié)果:
testInteger對(duì)象: TestInterger [integer=30] ---------------------------------- 成員屬性:java.lang.Integer,值:30 Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.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 java.lang.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這個(gè)int類(lèi)型的數(shù)據(jù)時(shí),在編譯階段編譯器會(huì)自動(dòng)進(jìn)行裝箱等同于
inField.set(testInterger, new Integer(90))。這樣運(yùn)行時(shí),獲取到的是Integer類(lèi)型的,能正常的傳入。
這里再來(lái)說(shuō)一下自動(dòng)拆箱裝箱的事情:
自動(dòng)裝箱是java編譯器在java原生類(lèi)型和對(duì)應(yīng)的對(duì)象包裝類(lèi)型上做的自動(dòng)轉(zhuǎn)換。
例如,把int 裝換成 Integer double轉(zhuǎn)換成Double等等。
如果是反過(guò)來(lái)轉(zhuǎn)換,那么叫做自動(dòng)拆箱,也是編譯器為我們做的事情。
強(qiáng)調(diào):自動(dòng)拆箱裝箱發(fā)生在編譯時(shí)刻,反射時(shí)發(fā)生在程序運(yùn)行時(shí)刻。
為了不混淆,利用反射修改包裝類(lèi)的值的時(shí)候,使用set方法,并且盡量手動(dòng)裝箱,也就是寫(xiě)成下面的形式:
inField.set(testInterger, new Integer(90))
常見(jiàn)錯(cuò)誤 2:反射非 public 的變量導(dǎo)致的 NoSuchFieldException
如果你使用 Class.getField() 或者 Class.getFields() 獲取非 public 的變量,編譯器會(huì)報(bào) java.lang.NoSuchFieldException 錯(cuò)。
常見(jiàn)錯(cuò)誤 3 :修改 final類(lèi)型的變量導(dǎo)致的 IllegalAccessException
當(dāng)你想要獲取或者修改 不可修改(final)的變量時(shí),會(huì)導(dǎo)致IllegalAccessException。
由于 Field 繼承自 AccessibleObject , 我們可以使用 AccessibleObject.setAccessible() 方法告訴安全機(jī)制,這個(gè)變量可以訪(fǎng)問(wèn)。
也就是Field.setAccessible(true)。告訴安全機(jī)制當(dāng)前的這字段不做訪(fǎng)問(wèn)權(quán)限檢查,這樣我們就能反射修改final修飾成常量了。
以上就是 Java 反射機(jī)制中 Field 的簡(jiǎn)要介紹和使用方法的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。如果想要了解更多關(guān)于 Java 反射的其他內(nèi)容,請(qǐng)關(guān)注W3Cschool,也希望大家多多支持。