Java reflection(反射)

基本翻譯自Java SE Tutorial中關於反射的章節:http://docs.oracle.com/javase/tutorial/reflect/index.html

Java的反射機制常被用於檢查或者修改在JVM中運行的程序的實時行爲,是非常高級的Java特性,如果不是對Java掌握的非常透徹,建議不要隨便使用!除此之外,反射是一個非常強大的技術,它可以讓你的應用擁有前所未有的能力:

可擴展性特性:使應用可以通過類的名稱來創建用戶自建的類對象。

類瀏覽器以及可視化開發環境:類瀏覽器可以查看類裏包含的所有成員。可視化開發環境利用反射提供的類型信息幫助開發人員寫出正確的代碼

調試器以及測試工具:調試器需要檢查類的私有成員。測試工具可利用反射機制系統的調用類中的一系列API,以單元測試保證代碼質量(TestNG, JUnit)


反射的缺點:反射會造成性能上的障礙、會帶來安全問題、會暴露出代碼的內部結構。如果可以用其他方法達到相同的目的,就不應使用反射!


1. 類相關的反射

每個對象要麼是一個引用類型要麼是一個主類型。所有引用類型都繼承自java.lang.Object,類、枚舉、數組已經接口都是引用類型,比如java.lang.String,所有主類型的包裝類像java.lang.Double,接口java.io.Serializable以及枚舉javax.swing.SortOrder。而主類型是固定的幾個:boolean, byte, short, int, long, char, float以及double。

JVM會爲每個類型的對象實例化一個不可變的java.lang.Class對象(就是說對每個類,JVM中都有一個類型爲Class的Object對應這個類),這個對象提供一些方法檢測這個對象運行時的屬性,比如成員和類型信息。Class類也提供創建新類和對象的功能。最重要的是,Class類(對每個其他類來說是Class對象)是所有反射API的入口。

1.1 獲取Class對象

所有反射操作都通過java.lang.Class進行,這個類及其實例化的對象是反射機制的入口。java.lang.reflect包中沒有一個類有公共的構建器。要取得這些類,我們需要調用Class類中的某些適當的方法。下面介紹一些獲取Class的方法,當然能不能獲取還要看代碼有沒有訪問這個對象、類名或者已經存在的Class的權限。

1.1.1 Object.getClass()

如果已經有了對象,最簡單的獲取其對應Class的方法是調用Object.getClass()方法。當然這個方法只能用於繼承自Object的引用類型。

Class h = "foo".getClass(); //返回String對應的Class對象

Class h = System.console().getClass(); //System.console()返回一個Console對象,它調用getClass()方法返回一個java.io.Console對象的Class對象。

enum E { A, B }

Class h = A.getClass(); //返回枚舉類型E對應的Class對象

import java.util.HashSet;
import java.util.Set;

Set<String> s = new HashSet<String>();
Class h = s.getClass(); //返回java.util.HashSet類對應的Class對象

1.1.2 通過.class語法獲取

如果沒有已經實例化的對象,那麼也可以通過將'.class'加到這個類型名的尾部來獲取對應的Class對象。這是獲取主類型Class對象的最簡方法。

boolean b;
//Class h = b.getClass(); //無法編譯
Class h = boolean.class; //獲得boolean對應的Class對象h

Class h = java.io.PrintStream.class; //獲取到java.io.PrintStream對應的Class對象

Class h = int[][][].class; //.class 也可用於獲取指定類型多維數組的Class對象


1.1.3 Class.forName()

如果我們知道某個類的全名,也可以調用Class類的靜態方法Class.forName()來獲取該類對應的Class對象。forName()方法不能用於主類型,但是可以用於使用主類型的數組。數組類型名字的語法可參考Class.getName()

Class h = Class.forName("com.hehe.MyClass"); //獲取MyClass對應的Class對象

Class hDoubleArray = Class.forName("[D"); //獲取類型爲主類型double的數組對應的Class對象(與double[].class等同)

Class hStringArray = Class.forName("[[Ljava.lang.String;"); //類型爲String的二維數組對應的Class對象,等同於String[][].class


1.1.4 主類型包裝類的類型域

除了.class方法獲取主類型的Class對象外,還可以通過主類型的包裝類來獲取。每個主類型已經void類型在java.lang包裏都有一個對應的包裝類,這些包裝類都有一個名叫TYPE的域,這個域對應了被包裝的主類型的Class對象。所以我們可以通過主類型的包裝類的TYPE域來獲取其Class對象。

Class h = Double.TYPE; //獲取主類型double對應的Class對象

Class h = Void.TYPE; //獲取void類型對應的Class對象


1.1.5 可以返回Class對象的方法

有一些可以獲取Class對象的方法,但是其中一些是在這個Class對象本身已經存在的情況下才能調用

Class.getSuperclass() 獲取這個Class對象的super類的Class對象。 Class h = javax.swing.JButton.class.getSuperclass(); //Javax.swing.JButton的super類是Javax.swing.AbstractButton

Class.getClasses() 獲取該類成員(包括繼承自父類的成員)中所有公共的類、接口、枚舉類型對應的Class對象,返回的是一個Class數組。Class<?>[] c = Character.class.getClasses(); 

Class.getDeclaredClasses() 獲取聲明在該類中的成員類、接口以及枚舉類型對應的所有Class對象,包括私有成員。

Class.getDeclaringClass()

java.lang.reflect.Field.getDeclaringClass()

java.lang.reflect.Method.getDeclaringClass()

java.lang.reflect.Constructor.getDeclaringClass()

這些方法返回聲明瞭這些成員的類的Class對象。匿名類聲明沒有這樣的包含類,但是卻有一個enclosing類。

import java.lang.reflect.Field;
Field f = System.class.getField("out");
Class h = f.getDeclaringClass(); //域out在類System中聲明
public class MyClass {
    static Object o = new Object() {
        public void m() {} 
    };
    static Class<c> = o.getClass().getDeclaringClass(); //匿名類對象的declaring class是null
} //匿名類對象有enclosing class,但是沒有declaring class
Class.getEnclosingClass() 返回這個類的enclosing class

Class c = Thread.State.class().getEnclosingClass(); //Thread.State域的enclosing class是Thread
public class MyClass {
    static Object o = new Object() { 
        public void m() {} 
    };
    static Class<c> = o.getClass().getEnclosingClass();
} //匿名類對象o的enclosing class是MyClass

1.2 檢查類修飾符和類型

一個類可能包含一個或多個修飾符,這些修飾符會影響到類的運行時行爲:

  • 訪問修飾符:public,protected,private
  • 覆蓋需求修飾符:abstract
  • 單一實例修飾符:static
  • 防修改修飾符:final
  • 浮點數行爲強制修飾符:strictfp

但是並非所有類都可以使用所有這些修飾符,比如接口不能爲final,枚舉不能爲abstract。修飾符的定義可以在這裏找到java.lang.reflect.Modifier。這個包也包含了解碼由Class.getModifiers()方法返回的修飾符集合的方法。

下例顯示怎樣獲取類的聲明組成,包括修飾符、泛型參數、被實現的接口、繼承路徑,由於Class類實現了java.lang.reflect.AnnotateElement接口,我們也可以獲取運行時annotations。

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;

public class ClassDeclarationSpy {
    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    out.format("Class:%n  %s%n%n", c.getCanonicalName());
	    out.format("Modifiers:%n  %s%n%n",
		       Modifier.toString(c.getModifiers()));

	    out.format("Type Parameters:%n");
	    TypeVariable[] tv = c.getTypeParameters();
	    if (tv.length != 0) {
		out.format("  ");
		for (TypeVariable t : tv)
		    out.format("%s ", t.getName());
		out.format("%n%n");
	    } else {
		out.format("  -- No Type Parameters --%n%n");
	    }

	    out.format("Implemented Interfaces:%n");
	    Type[] intfs = c.getGenericInterfaces();
	    if (intfs.length != 0) {
		for (Type intf : intfs)
		    out.format("  %s%n", intf.toString());
		out.format("%n");
	    } else {
		out.format("  -- No Implemented Interfaces --%n%n");
	    }

	    out.format("Inheritance Path:%n");
	    List<Class> l = new ArrayList<Class>();
	    printAncestor(c, l);
	    if (l.size() != 0) {
		for (Class<?> cl : l)
		    out.format("  %s%n", cl.getCanonicalName());
		out.format("%n");
	    } else {
		out.format("  -- No Super Classes --%n%n");
	    }

	    out.format("Annotations:%n");
	    Annotation[] ann = c.getAnnotations();
	    if (ann.length != 0) {
		for (Annotation a : ann)
		    out.format("  %s%n", a.toString());
		out.format("%n");
	    } else {
		out.format("  -- No Annotations --%n%n");
	    }

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

    private static void printAncestor(Class<?> c, List<Class> l) {
	Class<?> ancestor = c.getSuperclass();
 	if (ancestor != null) {
	    l.add(ancestor);
	    printAncestor(ancestor, l);
 	}
    }
}

output:

$ java ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap
Class:
  java.util.concurrent.ConcurrentNavigableMap

Modifiers:
  public abstract interface

Type Parameters:
  K V

Implemented Interfaces:
  java.util.concurrent.ConcurrentMap<K, V>
  java.util.NavigableMap<K, V>

Inheritance Path:
  -- No Super Classes --

Annotations:
  -- No Annotations --

類java.util.concurrent.ConcurrentNavigableMap的定義爲:

public interface ConcurrentNavigableMap<K,V>
    extends ConcurrentMap<K,V>, NavigableMap<K,V>
結果中修飾符有abstract,原因是接口默認就是abstract的,編譯器會爲每個接口都加上abstract修飾符。這個接口的定義包含兩個泛型參數:K和V。這個例子只是打印了這些參數的名字,但是使用java.lang.reflect.TypeVariabble中的方法還可以獲取更多信息。

下面是另一個輸入的結果,由於數組是運行時對象,所有的類型信息都由JVM定義。特別指出,數組都實現了Cloneable和java.io.Serializable接口,他們的父類總是Object:

$ java ClassDeclarationSpy "[Ljava.lang.String;"
Class:
  java.lang.String[]

Modifiers:
  public abstract final

Type Parameters:
  -- No Type Parameters --

Implemented Interfaces:
  interface java.lang.Cloneable
  interface java.io.Serializable

Inheritance Path:
  java.lang.Object

Annotations:
  -- No Annotations --
其他例子:

$ java ClassDeclarationSpy java.io.InterruptedIOException
Class:
  java.io.InterruptedIOException

Modifiers:
  public

Type Parameters:
  -- No Type Parameters --

Implemented Interfaces:
  -- No Implemented Interfaces --

Inheritance Path:
  java.io.IOException
  java.lang.Exception
  java.lang.Throwable
  java.lang.Object

Annotations:
  -- No Annotations --


$ java ClassDeclarationSpy java.security.Identity
Class:
  java.security.Identity

Modifiers:
  public abstract

Type Parameters:
  -- No Type Parameters --

Implemented Interfaces:
  interface java.security.Principal
  interface java.io.Serializable

Inheritance Path:
  java.lang.Object

Annotations:
  @java.lang.Deprecated()

1.3 類成員發現

Class中訪問類對象的域、方法和構建器的方法可以分爲兩類:1. 枚舉出類中成員和方法的方法; 2. 搜索特點成員的方法。同時,訪問直接定義在類中的成員和搜索繼承自父接口和父類中的成員的方法也有所不同。下表總結了所有成員定位(就是找到類成員)方法以及他們的特點。

定位域的Class方法:

Class API List of members? Inherited members? Private members?
getDeclaredField() no no yes
getField() no yes no
getDeclaredFields() yes no yes
getFields() yes yes no
定位方法的Class方法:

Class API List of members? Inherited members? Private members?
getDeclaredMethod() no no yes
getMethod() no yes no
getDeclaredMethods() yes no yes
getMethods() yes yes no
定位構建器的Class方法:

Class API List of members? Inherited members? Private members?
getDeclaredConstructor() no N/A1 yes
getConstructor() no N/A1 no
getDeclaredConstructors() yes N/A1 yes
getConstructors() yes N/A1 no
N/A: 構建器不是繼承自父類的

實例參見:http://docs.oracle.com/javase/tutorial/reflect/class/classMembers.html


1.4 故障排除

在使用反射時可能會遇到一些問題,下面列出一些典型的錯誤。

1.4.1 編譯警告:

Compiler Warning: "Note: ...uses unchecked or unsafe operations"

當調用方法是,參數類型需要檢查或者轉換,下面例子中getMethod()會產生一個典型的‘未檢查轉換警告’(unchecked conversion warning).

import java.lang.reflect.Method;

public class ClassWarning {
    void m() {
	try {
	    Class c = ClassWarning.class;
	    Method m = c.getMethod("m");  // warning

        // production code should handle this exception more gracefully
	} catch (NoSuchMethodException x) {
    	    x.printStackTrace();
    	}
    }
}
$ javac ClassWarning.java
Note: ClassWarning.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$ javac -Xlint:unchecked ClassWarning.java
ClassWarning.java:6: warning: [unchecked] unchecked call to getMethod
  (String,Class<?>...) as a member of the raw type Class
Method m = c.getMethod("m");  // warning
                      ^
1 warning
許多庫方法包括Class類本身都改爲使用泛型來定義了,由於代碼中變量c沒有定義類型,但是對應的getMethod()的參數是參數化類型,這樣就會發生unchecked conversion警告。有兩種解決方法:

a. 將變量c定義爲適當的泛型,此例中應定義如下:

Class<?> c = warn.getClass();

b. 或者使用@SuppressWarnings屏蔽警告

Class c = ClassWarning.class;
@SuppressWarnings("unchecked")
Method m = c.getMethod("m");  
// warning gone

1.4.2 無法訪問構建器: InstanitationException

如果一個類的默認構建器對外不可見,當調用Class.newInstance創建該類的實例時就會拋出InstanitationException。

class Cls {
    private Cls() {}
}

public class ClassTrouble {
    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName("Cls");
	    c.newInstance();  // InstantiationException

        // production code should handle these exceptions more gracefully
	} catch (InstantiationException x) {
	    x.printStackTrace();
	} catch (IllegalAccessException x) {
	    x.printStackTrace();
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }
}
$ java ClassTrouble
java.lang.IllegalAccessException: Class ClassTrouble can not access a member of
  class Cls with modifiers "private"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
        at java.lang.Class.newInstance0(Class.java:349)
        at java.lang.Class.newInstance(Class.java:308)
        at ClassTrouble.main(ClassTrouble.java:9)
Class.newInstance跟new關鍵字非常相似,產生此錯誤的原因也相同。典型的解決方法是利用java.lang.reflect.AccessibleObject類繞過訪問控制檢查,但是java.lang.Class類沒有繼承AccessibleObject類,所以此例中的解決方法爲修改代碼使用Constructor.newInstance(),關於Constructor.newInstance()的實例參考:http://docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html,這個例子下面也會講到。

2. 成員相關反射

反射定義了一個接口java.lang.reflect.Member,這個接口實現了3個接口java.lang.reflect.Field, java.lang.reflect.Method, java.lang.reflect.Constructor。對每一個成員,我們會介紹相關的獲取聲明和類型信息的API、針對這個成員的特定操作以及一般會遇到的問題。

Note:根據Java SE7語言規範,類成員就是一個類的可繼承組件包括域、方法、嵌套類、接口以及枚舉類型,由於構建器不是可繼承的所以不是成員。這個定義與反射中的成員定義是不同的。

2.1 域

域包含一個類型和一個值,java.lang.reflect.Field類提供了訪問給定對象的域的類型信息以及設置/獲取域值信息的方法

2.1.1 獲取域類型信息

域可以爲主類型或者引用類型,主類型是固定的8個:boolean、byte、short、int、long、char、float、double。引用類型是java.lang.Object的子類或者後代類,包括接口、數組以及枚舉類型。

下例打印出一個給定的全名類名稱和包含其中的域的類型和泛型信息

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的類型是一個二維數組,類型名的語法參考:http://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getName--

域val的類型是java.lang.Object,那是因爲在編譯時所有泛型相關的信息都通過類型清除移除掉了,所以T就會被上一層的類型變量替換,此例中爲java.lang.Object


2.1.2 獲取和解析域修飾符

以下修飾符可以修飾域:

  • 訪問控制修飾符:public、protected、private
  • 域專用運行時行爲控制修飾符:transient、volatile
  • 單例修飾符:static
  • 防止修改修飾符:final
  • Annotations

方法Field.getModifiers()可以用於獲取代表了聲明在該域上的修飾符集合的一個整數,整數的比特位所代表的修飾符定義在類java.lang.reflect.Modifier中

下例展示了怎樣搜索使用了給定修飾符的域,通過Field.isSynthetic()和Field.isEnumConstant()方法可以知道該域是synthetic(編譯器生成)的還是一個枚舉常量。

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)。可使用FIeld.isSynthetic()方法檢查一個域是否爲合成的。根據編譯器不同可能生成的合成域也不同,但是常用的有

this$0 用於內部類中引用最外的外部enclosing類

@VALUES 被枚舉用於實現隱含的靜態方法values()

synthetic fields包含在Class.getDeclaredFields()返回的集合中

Feild類實現了接口java.lang.reflect.AnnotatedElement,通過java.lang.annotation.RetentionPolicy.RUNTIME可以獲取運行時annotation,see:http://docs.oracle.com/javase/tutorial/reflect/class/classModifiers.html

2.1.3 獲取和設置域的值

我們可以利用反射設置一個類實例中包含的域的值,但是因爲這種方法違反了類的設計初衷,一般只在一些特殊情況下比如域值不能通過正常途徑設置時才使用,而且應該儘量避免使用。

下例展示了怎樣設置類型爲long,數組以及枚舉的域的值,更多方法參考Field

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

2.1.4 故障排除

無法轉換的類型:IllegalArgumentException
下例中Field.setInt被用於將一個Integer引用類型的域設置爲主類型int值,這樣就會產生一個IllegalArgumentException。正常賦值而非反射時,對於語句Integer val = 42;編譯器會將主類型42轉化成引用類型new Integer(42),所以賦值不會有問題。但是反射是實時發生的,沒有機制去封裝主類型。

import java.lang.reflect.Field;

public class FieldTrouble {
    public Integer val;

    public static void main(String... args) {
	FieldTrouble ft = new FieldTrouble();
	try {
	    Class<?> c = ft.getClass();
	    Field f = c.getDeclaredField("val");
  	    f.setInt(ft, 42);               // IllegalArgumentException

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

$ java FieldTrouble
Exception in thread "main" java.lang.IllegalArgumentException: Can not set
  java.lang.Object field FieldTrouble.val to (long)42
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
          (UnsafeFieldAccessorImpl.java:146)
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
          (UnsafeFieldAccessorImpl.java:174)
        at sun.reflect.UnsafeObjectFieldAccessorImpl.setLong
          (UnsafeObjectFieldAccessorImpl.java:102)
        at java.lang.reflect.Field.setLong(Field.java:831)
        at FieldTrouble.main(FieldTrouble.java:11)
解決方法爲修改代碼手動將主類型封裝成引用類型

f.set(ft, new Integer(43));


非公共域:NoSuchFieldException

還是使用上例的代碼,如果是訪問一個非公共的域就會失敗

$ java FieldSpy java.lang.String count
java.lang.NoSuchFieldException: count
        at java.lang.Class.getField(Class.java:1519)
        at FieldSpy.main(FieldSpy.java:12)
應該使用Class.getDeclaredFields()


修改final域:IllegalAccessException

當獲取或設置一個私有域(或者其他不能訪問的域),或者設置一個final域時(不管它的訪問修飾符是什麼),都會引發IllegalAccessException

import java.lang.reflect.Field;

public class FieldTroubleToo {
    public final boolean b = true;

    public static void main(String... args) {
	FieldTroubleToo ft = new FieldTroubleToo();
	try {
	    Class<?> c = ft.getClass();
	    Field f = c.getDeclaredField("b");
// 	    f.setAccessible(true);  // solution
	    f.setBoolean(ft, Boolean.FALSE);   // IllegalAccessException

        // production code should handle these exceptions more gracefully
	} catch (NoSuchFieldException x) {
	    x.printStackTrace();
	} catch (IllegalArgumentException x) {
	    x.printStackTrace();
	} catch (IllegalAccessException x) {
	    x.printStackTrace();
	}
    }
}
輸出結果:

$ java FieldTroubleToo
java.lang.IllegalAccessException: Can not set final boolean field
  FieldTroubleToo.b to (boolean)false
        at sun.reflect.UnsafeFieldAccessorImpl.
          throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:55)
        at sun.reflect.UnsafeFieldAccessorImpl.
          throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:63)
        at sun.reflect.UnsafeQualifiedBooleanFieldAccessorImpl.setBoolean
          (UnsafeQualifiedBooleanFieldAccessorImpl.java:78)
        at java.lang.reflect.Field.setBoolean(Field.java:686)
        at FieldTroubleToo.main(FieldTroubleToo.java:12)
Field擴展了AccessibleObject可以用來繞過這種檢查


2.2 方法

方法有返回值、參數、還有可能拋出異常,java.lang.reflect.Method類提供了一些方法獲取這些信息,以及調用一個給定對象中的方法

2.2.1 獲取方法類型信息

方法聲明部分包括方法名字、修飾符、參數、返回值以及可拋出的異常列表,java.lang.reflect.Method類提供了獲取這些信息的方法。

下例展示了怎樣枚舉一個類中所有的方法,並獲取每個方法的返回、參數、異常類型

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import static java.lang.System.out;

public class MethodSpy {
    private static final String  fmt = "%24s: %s%n";

    // for the morbidly curious
    <E extends RuntimeException> void genericThrow() throws E {}

    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    Method[] allMethods = c.getDeclaredMethods();
	    for (Method m : allMethods) {
		if (!m.getName().equals(args[1])) {
		    continue;
		}
		out.format("%s%n", m.toGenericString());

		out.format(fmt, "ReturnType", m.getReturnType());
		out.format(fmt, "GenericReturnType", m.getGenericReturnType());

		Class<?>[] pType  = m.getParameterTypes();
		Type[] gpType = m.getGenericParameterTypes();
		for (int i = 0; i < pType.length; i++) {
		    out.format(fmt,"ParameterType", pType[i]);
		    out.format(fmt,"GenericParameterType", gpType[i]);
		}

		Class<?>[] xType  = m.getExceptionTypes();
		Type[] gxType = m.getGenericExceptionTypes();
		for (int i = 0; i < xType.length; i++) {
		    out.format(fmt,"ExceptionType", xType[i]);
		    out.format(fmt,"GenericExceptionType", gxType[i]);
		}
	    }

        // production code should handle these exceptions more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }
}
輸出結果:

$ java MethodSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
  (java.lang.Class<?>[]) throws java.lang.NoSuchMethodException,
  java.lang.SecurityException
              ReturnType: class java.lang.reflect.Constructor
       GenericReturnType: java.lang.reflect.Constructor<T>
           ParameterType: class [Ljava.lang.Class;
    GenericParameterType: java.lang.Class<?>[]
           ExceptionType: class java.lang.NoSuchMethodException
    GenericExceptionType: class java.lang.NoSuchMethodException
           ExceptionType: class java.lang.SecurityException
    GenericExceptionType: class java.lang.SecurityException
方法的聲明爲:
public Constructor<T> getConstructor(Class<?>... parameterType)
注意方法的返回和參數類型都是泛型,Method.getGenericReturnType()會參考類文件中存在的簽名屬性,如果不存在,那他就會轉而調用Method.getReturnType(),這個方法沒有因爲泛型的引入而改變,其他形如getGenericFoo()的方法也類似。

再注意最後一個參數(其實是唯一的參數),parameterType,是類型爲java.lang.CLass的可變數量的參數,類型用java.lang.Class的一位數組代表。

下例使用了有返回類型爲泛型的方法:

$ java MethodSpy java.lang.Class cast
public T java.lang.Class.cast(java.lang.Object)
              ReturnType: class java.lang.Object
       GenericReturnType: T
           ParameterType: class java.lang.Object
    GenericParameterType: class java.lang.Object
泛型的返回類型爲class.java.lang.Object,那是因爲泛型的類型信息在編譯時被類型清除移除了。
下例展示了有多個重載的方法:

$ java MethodSpy java.io.PrintStream format
public java.io.PrintStream java.io.PrintStream.format
  (java.util.Locale,java.lang.String,java.lang.Object[])
              ReturnType: class java.io.PrintStream
       GenericReturnType: class java.io.PrintStream
           ParameterType: class java.util.Locale
    GenericParameterType: class java.util.Locale
           ParameterType: class java.lang.String
    GenericParameterType: class java.lang.String
           ParameterType: class [Ljava.lang.Object;
    GenericParameterType: class [Ljava.lang.Object;
public java.io.PrintStream java.io.PrintStream.format
  (java.lang.String,java.lang.Object[])
              ReturnType: class java.io.PrintStream
       GenericReturnType: class java.io.PrintStream
           ParameterType: class java.lang.String
    GenericParameterType: class java.lang.String
           ParameterType: class [Ljava.lang.Object;
    GenericParameterType: class [Ljava.lang.Object;
重名的重載方法都會包含到Class.getDeclaredMethods()的返回結果中。

2.2.2 獲取方法的參數名字

翻譯真是累人的活,先到這吧。。。


2.2.3 獲取並解析方法的修飾符


2.2.4 調用方法


2.2.5 故障排除



2.3 構建器



3. 數組和枚舉類型

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