Java反射基礎(二)--Fileds對象的使用

在說Filed之前,我們先來了解一下Member接口. 反射中定義了一個接口 java.lang.reflect.Member . java.lang.reflect.Field, java.lang.reflect.Method, 和java.lang.reflect.Constructor 都實現了該接口.我們將在接下來的部分介紹這些類.對於每個Member, 我們都會介紹相關的API去獲取和操作該Member.每個概念我們都會使用示例代碼和示例輸出說明.

 

1.獲得字段(Field)的類型

一個filed可以是一個基本數據類型或者一個引用類型.java中有八種基本的數據類型: boolean, byte, short, int, long, char, float double.引用類型可以使任何的直接或者間接的繼承java.lang.Object 的接口,數組,或者枚舉等.

FiledSpy示例類實現了將一個給定的類的二進制文件中包含的field類型和泛型打印出來.

import java.lang.reflect.Field;
import java.util.List;

public class FieldSpy<T> {
    public boolean[][] b = {{ false, false }, { true, true } };
    public String name  = "Alice";
    public List<Integer> list;
    public T val;

    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    Field f = c.getField(args[1]);
	    System.out.format("Type: %s%n", f.getType());
	    System.out.format("GenericType: %s%n", f.getGenericType());

        // production code should handle these exceptions more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	} catch (NoSuchFieldException x) {
	    x.printStackTrace();
	}
    }
}

實例輸出:

$ java FieldSpy FieldSpy b
Type: class [[Z
GenericType: class [[Z
$ java FieldSpy FieldSpy name
Type: class java.lang.String
GenericType: class java.lang.String
$ java FieldSpy FieldSpy list
Type: interface java.util.List
GenericType: java.util.List<java.lang.Integer>
$ java FieldSpy FieldSpy val
Type: class java.lang.Object
GenericType: T

字段b的類型是一個二維的布爾型數組.

字段val的類型被認定爲java.lang.Object.因爲泛型的實現方式是在編譯的過程中將和泛型有關的信息用相關的類替換.因此此處爲java.lang.Object.

 

2.獲取和解析字段修飾符(Filed Modifier)

java中有以下幾種字段修飾符:

  • 訪問控制修飾符: public, protected, and private
  • 運行時領域管理修飾符: transient and volatile
  • 控制一個實例修飾符: static
  • 禁止值修改修飾符: final
  • 註解

方法Field.getModifiers()可以用來獲得一個以整型表示的字段修飾符.這些整形被定義在java.lang.reflect.Modifier中.

示例類FieldModifierSpy說明了如何搜索一個類中指定的修飾符所修飾的字段.

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import static java.lang.System.out;

enum Spy { BLACK , WHITE }

public class FieldModifierSpy {
    volatile int share;
    int instance;
    class Inner {}

    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    int searchMods = 0x0;
	    for (int i = 1; i < args.length; i++) {
		searchMods |= modifierFromString(args[i]);
	    }

	    Field[] flds = c.getDeclaredFields();
	    out.format("Fields in Class '%s' containing modifiers:  %s%n",
		       c.getName(),
		       Modifier.toString(searchMods));
	    boolean found = false;
	    for (Field f : flds) {
		int foundMods = f.getModifiers();
		// Require all of the requested modifiers to be present
		if ((foundMods & searchMods) == searchMods) {
		    out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n",
			       f.getName(), f.isSynthetic(),
			       f.isEnumConstant());
		    found = true;
		}
	    }

	    if (!found) {
		out.format("No matching fields%n");
	    }

        // production code should handle this exception more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }

    private static int modifierFromString(String s) {
	int m = 0x0;
	if ("public".equals(s))           m |= Modifier.PUBLIC;
	else if ("protected".equals(s))   m |= Modifier.PROTECTED;
	else if ("private".equals(s))     m |= Modifier.PRIVATE;
	else if ("static".equals(s))      m |= Modifier.STATIC;
	else if ("final".equals(s))       m |= Modifier.FINAL;
	else if ("transient".equals(s))   m |= Modifier.TRANSIENT;
	else if ("volatile".equals(s))    m |= Modifier.VOLATILE;
	return m;
    }
}

示例輸出:

$ java FieldModifierSpy FieldModifierSpy volatile
Fields in Class 'FieldModifierSpy' containing modifiers:  volatile
share    [ synthetic=false enum_constant=false ]

$ java FieldModifierSpy Spy public
Fields in Class 'Spy' containing modifiers:  public
BLACK    [ synthetic=false enum_constant=true  ]
WHITE    [ synthetic=false enum_constant=true  ]

$ java FieldModifierSpy FieldModifierSpy\$Inner final
Fields in Class 'FieldModifierSpy$Inner' containing modifiers:  final
this$0   [ synthetic=true  enum_constant=false ]

$ java FieldModifierSpy Spy private static final
Fields in Class 'Spy' containing modifiers:  private static final
$VALUES  [ synthetic=true  enum_constant=false ]

注意到一些字段被顯示出來,雖然他們並沒有被定義在該類的源代碼中.原因是編譯器會自動生成一些字段(synthetic fields  :這些字段指的是不是有用戶顯示聲明的,而是在編譯的時候,由編譯器合成的).如果你想要知道一個字段是否是合成的(synthetic), 也已使用Field.isSynthetic()方法.合成字段的集合是依賴於編譯器的.然而普遍的使用this$0在內部類中表示最外層的封裝類.枚舉中使用$VALUES類定義隱式的靜態方法values().合成類的名稱不一定總是一樣的,不同的編譯器可能有不同的名字.並且並不是所有的合成字段都會被聲明爲public.

因爲Field實現了java.lang.reflect.AnnotatedElement接口,因此我們也可以使用java.lang.annotation.RetentionPolicy.RUNTIME獲取運行時註解.具體示例見Examining Class Modifiers and Types..

 

3.獲取和設置字段值.

給我們一個Class實例,我們可以使用反射去修改字段的值.這經常被使用在不能通過通常的方式修改該字段的值的環境下.因爲這樣的操作通常違反類的設計意圖,這應該被謹慎的使用.

 

Book示例類說明了如何設置long, array, enum類型的字段值.其他類型的對應方法,參考java API.

import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;

enum Tweedle { DEE, DUM }

public class Book {
    public long chapters = 0;
    public String[] characters = { "Alice", "White Rabbit" };
    public Tweedle twin = Tweedle.DEE;

    public static void main(String... args) {
	Book book = new Book();
	String fmt = "%6S:  %-12s = %s%n";

	try {
	    Class<?> c = book.getClass();

	    Field chap = c.getDeclaredField("chapters");
	    out.format(fmt, "before", "chapters", book.chapters);
  	    chap.setLong(book, 12);
	    out.format(fmt, "after", "chapters", chap.getLong(book));

	    Field chars = c.getDeclaredField("characters");
	    out.format(fmt, "before", "characters",
		       Arrays.asList(book.characters));
	    String[] newChars = { "Queen", "King" };
	    chars.set(book, newChars);
	    out.format(fmt, "after", "characters",
		       Arrays.asList(book.characters));

	    Field t = c.getDeclaredField("twin");
	    out.format(fmt, "before", "twin", book.twin);
	    t.set(book, Tweedle.DUM);
	    out.format(fmt, "after", "twin", t.get(book));

        // production code should handle these exceptions more gracefully
	} catch (NoSuchFieldException x) {
	    x.printStackTrace();
	} catch (IllegalAccessException x) {
	    x.printStackTrace();
	}
    }
}

示例輸出:

$ java Book
BEFORE:  chapters     = 0
 AFTER:  chapters     = 12
BEFORE:  characters   = [Alice, White Rabbit]
 AFTER:  characters   = [Queen, King]
BEFORE:  twin         = DEE
 AFTER:  twin         = DUM

注意: 通過反射設置字段的值往往需要很多的操作.因爲很多額外的操作必須被執行,例如檢測數據的可訪問性.但是從運行時的角度來看,結果是一樣的.因爲所有的操作被看做一個院子操作來執行,等同於直接修改該變量的值.

e:使用反射會導致一些運行時優化失效.例如,下面的代碼很容易被java虛擬機優化:
int x = 1;
x = 2;
x = 3;

但是如果使用反射,則要使用Field.set*(),這是優化失效.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章