1. 程式人生 > >java 反射(Reflection)和內省(Introspector)

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.ReflectPermissionjava.lang.reflect裡的其他類都沒有public構造器。為了得到java.lang.reflect的其他類(如Method,ArrayField),就得呼叫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物件,並顯示它們的名稱。