java 反射(Reflection)和內省(Introspector)
java中所有物件(object)不是引用型別(reference)就是基本型別(primitive)。不管什麼型別物件,java虛擬機器都會為之例項化一個java.lang.Class的不可變例項(Class類的例項就是執行的java應用裡的classes和interfaces),這個例項會提供方法來檢測物件的成員(members )和型別(type)資訊。Class也有能力建立新的classes和objects。最重要的是Class是所有反射(Reflection )APIs的入口。
tip:java.lang.Class<T>繼承自java.lang.Object第一部分:獲得類
除了java.lang.reflect.ReflectPermission,java.lang.reflect裡的其他類都沒有public構造器。為了得到java.lang.reflect的其他類(如Method,Array,Field),就得呼叫Class中的合適的方法。依據我們的程式碼是否可以訪問object,類的名字,型別,存在的類Class(an object, the name of class, a type, or an existing Class),我們有幾種得到Class的方式。下面依次介紹:
Object.getClass() 這種方式只對引用型別有效。
1.Class c = "a string".getClass();// 返回String的類。
2.
import java.util.HashSet;
import java.util.Set;
Set<String> s = new HashSet<String>();
Class c = s.getClass();// 返回java.util.HashSet類。
The .class Syntax
如果無法獲得例項但是可以獲得型別,我們就可以通過在Class後面加".class"來得到此類的Class。這也是基礎型別(primitive type)獲得相應Class最容易的方式。boolean b;
Class c = b.getClass(); // 編譯時錯誤
Class c = boolean.class; // 正確
Class c = int[][][].class;// 正確
Class.forName()如果知道一個類的完整路徑,那麼也可以通過靜態方法Class.forName()獲得相應的Class。但這不能用於基礎型別。
Class c = Class.forName("com.duke.MyLocaleServiceProvider");
Class cDoubleArray = Class.forName("[D"); // 返回的型別與 double[].class 一致。
Class cStringArray = Class.forName("[[Ljava.lang.String;"); // String[][].class
TYPE Field for Primitive Type Wrappers
基本型別可以通過".class"語法獲得相應的類,但也可以通過其包裹類的TYPE屬性(field)獲得。
Class c = Double.TYPE; // 即 double.class
Class c = Void.TYPE; // void.class
Methods that Return Classes
Class.getSuperclass()
Class.getClasses() 返回此類中的公開的classes,interfaces 和enums成員。
Class<?>[] c = Character.class.getClasses();
還有其他的等等。
第二部分:檢測類(Class)的修飾符和型別
訪問修飾符:public,protected,private
需要重寫的修飾符: abstract
限制只能有一個例項的修飾符:static
防止值被修改的修飾符:final
強制嚴格的浮點行為的修飾符:strictfp
註釋Annotations
java.lang.reflect.Modifier包含了所有修飾符的宣告。也包含了由 Class.getModifiers()返回的修飾符集合的 解碼的方法。
Modifier.toString(c.getModifiers()); // c 是一個類
TypeVariable[] tv = c.getTypeParameters();
Type[] intfs = c.getGenericInterfaces();
列印結果如下:
$ 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 --
第三部分:發現類成員(Members)
Class 提供的訪問fields,methods和constructors的方法(methods)可以分成兩種型別:列舉所有成員的方法(methods)和尋找特殊成員的方法(methods)。
Class Methods for Locating Fields
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 Methods for Locating Methods
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 Methods for Locating Constructors
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 |
1 Constructors are not inherited.
c.getPackage();
c.getConstructors();
c.getFields();
c.getMethods();
列印結果如下:
$ java ClassSpy java.lang.ClassCastException CONSTRUCTOR
Class:
java.lang.ClassCastException
Package:
java.lang
Constructor:
public java.lang.ClassCastException()
public java.lang.ClassCastException(java.lang.String)
或者如下:
$ java ClassSpy java.nio.channels.ReadableByteChannel METHOD
Class:
java.nio.channels.ReadableByteChannel
Package:
java.nio.channels
Methods:
public abstract int java.nio.channels.ReadableByteChannel.read
(java.nio.ByteBuffer) throws java.io.IOException
public abstract void java.nio.channels.Channel.close() throws
java.io.IOException
public abstract boolean java.nio.channels.Channel.isOpen()
《Think in java》中寫道
“
Class 類支援反射的概念,Java附帶的庫java.lang.reflect包含了Field,Method以及Constructor類(每個類都實現了Member介面)。這些型別的物件是由JVM在執行期建立的,用以表示未知類裡對應的成員。這樣你就可以使用Constructor建立新的物件,用get()和set()方法讀取和修改與Field物件關聯的屬性,用invoke()方法呼叫與Method物件關聯的方法。另外,你還可以呼叫getFields(),getMethods(),getConstructors()等等很便利的方法,以返回表示屬性、方法以及構造器的物件陣列,這些物件(在JDK文件中,可找到與Class類相關的更多的資料)。這樣,匿名物件的類資訊就能在執行期被完全確定下來,而在編譯期不需要知道任何事情。
重要的是,反射機制並沒有什麼魔法。當你通過反射與一個未知型別的物件打交道時,JVM只是簡單地檢查這個物件,看它屬於哪個特定的類(就象RTTI那樣)。但在這之後,在做其它事情之前,必須載入那個類的Class物件。因此,那個類的.class檔案對於JVM來說必須是可獲取的,要麼在本地機器上,要麼可以通過網路取得。所以RTTI和反射之間真正的區別只在於,對RTTI來說,編譯器在編譯期開啟和檢查.class檔案。(換句話說,我們可以用“普通”方式呼叫一個物件的所有方法。)而對於反射機制來說.class檔案在編譯期間是不可獲取的,所以是在執行期開啟和檢查.class檔案。
”應用:使用內省器(Introspector)來抽取出BeanInfo
JavaBean模型最關鍵部分之一,發生在當你從選用區拖動一個Bean,然後把它放置到窗體上的時候。應用程式構建工具必須能夠建立這個Bean(如果有預設構造器就可以建立),然後在不訪問Bean的原始碼的情況下,抽取出所有必要資訊,以建立屬性和事件處理器的列表。
部分解決方案在第 10 章就出現了:Java的反射機制能發現未知類的所有方法。對於解決JavaBean的這個問題,這是個完美的方案,你不用像其它視覺化程式語言那樣使用任何語言附加的關鍵字。實際上,Java語言里加入反射機制的主要原因之一就是為了支援JavaBean(儘管反射也支援物件序列化和遠端方法呼叫)。所以,你也許會認為應用程式構建工具的編寫者將使用反射來抽取Bean的方法,然後在方法裡面查找出Bean的屬性和事件。
這當然是可行的,不過Java的設計者希望提供一個標準工具,不僅要使Bean用起來簡單,而且對於建立更復雜的Bean,能夠提供一個標準方法。這個工具就是Introspector類,這個類最重要的就是靜態的getBeanInfo( )方法。你向這個方法傳遞一個Class物件引用,它能夠完全偵測這個類,然後返回一個BeanInfo物件,你可以通過這個物件得到Bean的屬性、方法和事件。
程式碼片段:
BeanInfo bi = null;
bi = Introspector.getBeanInfo(bean, Object.class);
PropertyDescriptor[] properties =
bi.getPropertyDescriptors();
for(int i = 0; i < properties.length; i++) { Class p = properties[i].getPropertyType(); if(p == null) continue; print("Property type:\n " + p.getName() +
"Property name:\n " + properties[i].getName()); Method readMethod = properties[i].getReadMethod(); if(readMethod != null)
print("Read method:\n " + readMethod); Method writeMethod = properties[i].getWriteMethod(); if(writeMethod != null)
print("Write method:\n " + writeMethod); print("====================");
} print("Public methods:"); MethodDescriptor[] methods = bi.getMethodDescriptors(); for(int i = 0; i < methods.length; i++)
print(methods[i].getMethod().toString()); print("======================");
列印結果:
class name: Frog Property type:
Color
Property name:
color
Read method:
public Color getColor() Write method:
public void setColor(Color) ==================== Property type:
Spots
Property name:
spots
Read method:
public Spots getSpots() Write method:
public void setSpots(Spots) ====================
建立一個BeanInfo物件,成功的話,就呼叫BeanInfo的方法得到有關其屬性、方法和事件的資訊。你會發現Introspector.getBeanInfo( )方法有第二個引數,它用來告訴Introspector在哪個繼承層次上停止查詢。因為我們不關心來自Object的方法,所以這裡的引數讓Introspector在解析來自Object的所有方法前停止查詢。
對於屬性來說,getPropertyDescriptors( )返回型別為PropertyDescriptor的陣列,你可以針對每一個PropertyDescriptor都呼叫getPropertyType( )來得到“通過屬性方法設定和返回的物件”型別。然後,針對每個屬性,你可以通過getName( )方法得到它的別名(從方法名中抽取),通過getReadMethod( )方法得到讀方法,通過getWriteMethod( )方法得到寫方法。後兩個方法返回Method物件,它們能夠用來在物件上呼叫相應的方法(這是反射的一部分)。
對於公共方法(包括屬性方法),getMethodDescriptors( )方法返回型別為MethodDescriptor的陣列。對於陣列的每個元素,你可以得到相關聯的Method物件,並顯示它們的名稱。