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

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 對象所表示字段的聲明類型。
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,thisClassName.lastIndexOf("."));
  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: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類的字段的類型和修飾符:

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  |類型: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.基本類型的獲取方法:

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 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對象的值:");
  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 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對象的值:");
        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=小明]
---------------------------------
通過反射獲取字段的值:
字段: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的方法:

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 :無法轉換類型導致的 java.lang.IllegalArgumentException

在使用反射獲取或者修改一個變量的值時,編譯器不會進行自動裝/拆箱。所以我們無法給 Integer 類型的屬性使用 setInt() 方法重新設值,必須給它賦一個 Integer 對象才可以。

否則會因為無法轉換類型而出現java.lang.IllegalArgumentException

實例:

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類型的,會自動裝箱成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]
----------------------------------
成員屬性: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這個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 的變量,編譯器會報 java.lang.NoSuchFieldException 錯。

常見錯誤 3 :修改 final類型的變量導致的 IllegalAccessException

當你想要獲取或者修改 不可修改(final)的變量時,會導致IllegalAccessException。

由於 Field 繼承自 AccessibleObject , 我們可以使用 AccessibleObject.setAccessible() 方法告訴安全機制,這個變量可以訪問。

也就是Field.setAccessible(true)。告訴安全機制當前的這字段不做訪問權限檢查,這樣我們就能反射修改final修飾成常量瞭。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。