App下載

簡(jiǎn)述Java反射Field類 及全方位解析使用方法

猿友 2021-07-20 10:31:24 瀏覽數(shù) (3233)
反饋

在 Java 語言中的反射機(jī)制非常之重要,作為反射可用方法之一的 Field 類提供有關(guān)類或接口的單個(gè)字段的信息,以及對(duì)它的動(dòng)態(tài)訪問權(quán)限。反射的字段可能是一個(gè)類(靜態(tài))字段或?qū)嵗侄巍1疚膶⒑痛蠹曳窒硪幌?Java 反射機(jī)制中的 Field 類和具體使用方法。

Field 成員變量的介紹

每個(gè)成員變量有類型和值。

java.lang.reflect.Field 為我們提供了獲取當(dāng)前對(duì)象的成員變量的類型,和重新設(shè)值的方法。

獲取變量的類型

類中的變量分為兩種類型:基本類型和引用類型:

基本類型( 8 種)

整數(shù):byte, short, int, long

浮點(diǎn)數(shù):float, double

字符:char

布爾值:boolean

引用類型

所有的引用類型都繼承自 java.lang.Object

類,枚舉,數(shù)組,接口都是引用類型

java.io.Serializable 接口,基本類型的包裝類(比如 java.lang.Double)也是引用類型

java.lang.reflect.Field 提供了兩個(gè)方法獲去變量的類型:

Field.getType():返回這個(gè)變量的類型

Field.getGenericType():如果當(dāng)前屬性有簽名屬性類型就返回,否則就返回 Field.getType()

實(shí)例:

Class<?> getType() 
          返回一個(gè) Class 對(duì)象,它標(biāo)識(shí)了此 Field 對(duì)象所表示字段的聲明類型。
Type getGenericType() 
          返回一個(gè) Type 對(duì)象,它表示此 Field 對(duì)象所表示字段的聲明類型。

測(cè)試類:

public class A
{
 public String id;
 protected String name;
 int age;
 private String sex;
 int[][] ints;
}

main方法:

public class Test
{ 
 /**
  * 返回該類所在包的包名字字符串
  * @param thisClass 一個(gè)類的Class對(duì)象
  * @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類,然后獲取規(guī)范化的名字
   System.out.printf("Type:%-18s|",field.getType().getCanonicalName());
   //獲取字段的類型的Type類對(duì)象,然后獲取類的名字
   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)前字段的類型。這兩個(gè)方法的區(qū)別還是在返回類型上

getType()方法返回的是Class<?>類型的,而getGenericType()返回的是Type類型的。

獲取成員變量的修飾符

成員變量可以被以下修飾符修飾:

訪問權(quán)限控制符:public, protected, private

限制只能有一個(gè)實(shí)例的:static

不允許修改的:final

不會(huì)被序列化:transient

線程共享數(shù)據(jù)的一致性:volatile

注解

類似獲取 Class 的修飾符,我們可以使用 Field.getModifiers() 方法獲取當(dāng)前成員變量的修飾符。

返回 java.lang.reflect.Modifier 中定義的整形值。然后使用 Modifier.toString(int mod)解碼成字符串

實(shí)例:獲取上面的A類的字段的類型和修飾符:

public static void printField(Class<?> class1)
{
 // 獲取類的所有聲明的字段
 Field[] sField = class1.getDeclaredFields();
 for (Field field : sField)
 {
  // 獲取字段的名字
  System.out.printf("字段:%-4s|", field.getName());
  // 獲取字段的類型的Class類,然后獲取規(guī)范化的名字
  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);    
}

運(yùn)行結(jié)果:


字段:id  |類型:java.lang.String  |修飾符:public
字段:name|類型:java.lang.String  |修飾符:protected
字段:age |類型:int               |修飾符:
字段:sex |類型:java.lang.String  |修飾符:private
字段:ints|類型: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í)來說,反射修改變量值的操作和類中修改變量的結(jié)果是一樣的。

1.基本類型的獲取方法:


byte getByte(Object obj) 
          獲取一個(gè)靜態(tài)或?qū)嵗?byte 字段的值。 
int getInt(Object obj) 
          獲取 int 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 int 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹怠?
 short getShort(Object obj) 
          獲取 short 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 short 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹怠?
 long getLong(Object obj) 
          獲取 long 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 long 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹怠?
 float getFloat(Object obj) 
          獲取 float 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 float 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹怠?
 double getDouble(Object obj) 
          獲取 double 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 double 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹怠?
 boolean getBoolean(Object obj) 
          獲取一個(gè)靜態(tài)或?qū)嵗?boolean 字段的值。 
 char getChar(Object obj) 
          獲取 char 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 char 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹怠?/code>

2.基本類型的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.引用類型的getters方法:

Object get(Object obj) 

返回指定對(duì)象上此 Field 表示的字段的值。

4.引用類型的setters方法:

void set(Object obj, Object value) 

將指定對(duì)象變量上此 Field 對(duì)象表示的字段設(shè)置為指定的新值。

實(shí)例:

(1)基本類型的獲取和修改:

測(cè)試類:

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對(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)引用類型的獲取和修改

測(cè)試類:

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è)試的類B的字段都是private類型的,在外部類是無法直接訪問到這些成員屬性的,想要獲取和修改只能通過B類的getters和setters方法進(jìn)行。使用反射我可以繞過這些限制,因?yàn)槭撬接蓄愋偷囊褂?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對(duì)象的值:");
        System.out.println(b);
        System.out.println("---------------------------------");
        Class bClass=b.getClass();
        //獲取共有的字段列表
        Field[] bFields=bClass.getDeclaredFields();
        System.out.println("通過反射獲取字段的值:");
        //獲取字段的值,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=小明]
---------------------------------
通過反射獲取字段的值:
字段:java.lang.String,值:B1000
字段:java.lang.String,值:小明
---------------------------------
現(xiàn)在的b對(duì)象的值:
B [id=哈哈, Name=哈哈]

再說一下setAccessible()方法,F(xiàn)ield的setAccessible()方法是從AccessibleObject類繼承而來的。AccessibleObject 類是 Field、Method 和 Constructor 對(duì)象的基類。

它提供了在使用時(shí) 取消默認(rèn) Java 語言訪問控制檢查的能力。

一般情況下,我們并不能對(duì)類的私有字段進(jìn)行操作,利用反射也不例外,但有的時(shí)候,例如要序列化的時(shí)候,我們又必須有能力去處理這些字段,這時(shí)候,我們就需要調(diào)用AccessibleObject上的setAccessible()方法來允許這種訪問,而由于反射類中的Field,Method和Constructor繼承自AccessibleObject,因此,通過在Field,Method和Constructor這些類上調(diào)用setAccessible()方法,我們可以操作這些字段無法訪問的字段。

返回boolean的方法:

boolean equals(Object obj) 
          將此 Field 與指定對(duì)象比較。 
boolean isEnumConstant() 
          如果此字段表示枚舉類型的元素,則返回 true;否則返回 false。 
boolean isSynthetic() 
          如果此字段是復(fù)合字段,則返回 true;否則返回 false。

返回String的方法:

String getName() 
         返回此 Field 對(duì)象表示的字段的名稱。 
String toGenericString() 
         返回一個(gè)描述此 Field(包括其一般類型)的字符串。 
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)
          如果存在該元素的指定類型的注釋,則返回這些注釋,否則返回 null。
Annotation[] getDeclaredAnnotations()
          返回直接存在于此元素上的所有注釋。

3.返回字段所在的類或者接口的Class對(duì)象

 Class<?> getDeclaringClass()
          返回表示類或接口的 Class 對(duì)象,該類或接口聲明由此 Field 對(duì)象表示的字段。

4.返回字段的類型(Type)

Type getGenericType()
          返回一個(gè) Type 對(duì)象,它表示此 Field 對(duì)象所表示字段的聲明類型。 

5.返回修飾符編碼:這個(gè)方法上面已經(jīng)提到了,可以使用Modifier.toString(int mod)方法,把獲取到的編碼轉(zhuǎn)換成修飾符字符串

 int getModifiers()
以整數(shù)形式返回由此 Field 對(duì)象表示的字段的 Java 語言修飾符。 

常見錯(cuò)誤 1 :無法轉(zhuǎn)換類型導(dǎo)致的 java.lang.IllegalArgumentException

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

否則會(huì)因?yàn)闊o法轉(zhuǎn)換類型而出現(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類型的,會(huì)自動(dòng)裝箱成Integer類型
        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);// 不做訪問控制檢查
        // 獲取字段的值
        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類型的數(shù)據(jù)時(shí),在編譯階段編譯器會(huì)自動(dòng)進(jìn)行裝箱等同于

inField.set(testInterger, new Integer(90))。這樣運(yùn)行時(shí),獲取到的是Integer類型的,能正常的傳入。

這里再來說一下自動(dòng)拆箱裝箱的事情:

自動(dòng)裝箱是java編譯器在java原生類型和對(duì)應(yīng)的對(duì)象包裝類型上做的自動(dòng)轉(zhuǎn)換。

例如,把int 裝換成 Integer double轉(zhuǎn)換成Double等等。

如果是反過來轉(zhuǎn)換,那么叫做自動(dòng)拆箱,也是編譯器為我們做的事情。

強(qiáng)調(diào):自動(dòng)拆箱裝箱發(fā)生在編譯時(shí)刻,反射時(shí)發(fā)生在程序運(yùn)行時(shí)刻。

為了不混淆,利用反射修改包裝類的值的時(shí)候,使用set方法,并且盡量手動(dòng)裝箱,也就是寫成下面的形式:

inField.set(testInterger, new Integer(90))

常見錯(cuò)誤 2:反射非 public 的變量導(dǎo)致的 NoSuchFieldException

如果你使用 Class.getField() 或者 Class.getFields() 獲取非 public 的變量,編譯器會(huì)報(bào) java.lang.NoSuchFieldException 錯(cuò)。

常見錯(cuò)誤 3 :修改 final類型的變量導(dǎo)致的 IllegalAccessException

當(dāng)你想要獲取或者修改 不可修改(final)的變量時(shí),會(huì)導(dǎo)致IllegalAccessException。

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

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

以上就是 Java 反射機(jī)制中 Field 的簡(jiǎn)要介紹和使用方法的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。如果想要了解更多關(guān)于 Java 反射的其他內(nèi)容,請(qǐng)關(guān)注W3Cschool,也希望大家多多支持。


0 人點(diǎn)贊