Java反射學習小記

Java反射機制主要提供瞭如下功能:java

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所具備的成員變量和方法
  • 在運行時調用任意一個對象的方法
  • 生成動態代理

不少框架都用到了反射機制,包括大名鼎鼎的Spring。所以,瞭解反射也能夠說是爲以後學習框架源碼而打下堅實的基礎。編程

即使編譯時不知道類型和方法名稱,也能使用反射。反射使用類對象提供的基本元數據,能從類對象中找出方法或字段的名稱,而後獲取表示方法或字段的對象。數組

在Java中,靜態成員和普通數據類型不是對象,其餘皆是。瀏覽器

那麼問題來了,類是誰的對象?緩存

java.lang.Class的實例對象。安全

Class.forName(ClassName)//能夠動態加載類——也就是運行時加載

(使用 Class::newInstance() 或另外一個構造方法)建立實例時也能讓實例具備反射功能。若是有一個能反射的對象和一個 Method 對象,咱們就能在以前類型未知的對象上調用任何方法。架構

反射出來的對象信息是幾乎未知的,因此反射也並非那麼的好用。框架

何時用反射

不少,也許是多數 Java 框架都會適度使用反射。若是編寫的架構足夠靈活,在運行時以前都不知道要處理什麼代碼,那麼一般都須要使用反射。例如,插入式架構、調試器、代碼瀏覽器和 REPL 類環境每每都會在反射的基礎上實現。ide

反射在測試中也有普遍應用,例如,JUnit 和 TestNG 庫都用到了反射,並且建立模擬對象也要使用反射。若是你用過任何一個 Java 框架,即使沒有意識到,也幾乎能夠肯定,你使用的是具備反射功能的代碼。函數

常見的反射手段有JDK反射和cglib反射。

在本身的代碼中使用反射 API 時必定要知道,獲取到的對象幾乎全部信息都未知,所以處理起來可能很麻煩。

只要知道動態加載的類的一些靜態信息(例如,加載的類實現一個已知的接口),與這個類交互的過程就能大大簡化,減輕反射操做的負擔。

使用反射時有個常見的誤區:試圖建立能適用於全部場合的反射框架。正確的作法是,只處理當前領域當即就能解決的問題。

如何使用反射

使用反射的第一步就是獲取Class對象,Class對象裏存儲了不少關鍵信息——畢竟這是用來描述類的class。

咱們能夠這樣來獲取Class信息:

Class<?> clz = Class.forName(obj.getClazz());
//經過class生成相應的實例
Object newObj = clz.newInstance
從Java1.5開始,Class類就支持泛型化了。好比:String.class就是Class<String>類型。

Method對象

在反射最經常使用的API就是Method了。

類對象中包含該類中每一個方法的 Method 對象。這些 Method 對象在類加載以後惰性建立,因此在 IDE 的調試器中不會當即出現。

Method對象中保存的方法和元數據:

private Class<?>                   clazz;
private int                        slot;
// This is guaranteed to be interned by the VM in the 1.4
// reflection implementation
private String                     name;
private Class<?>                   returnType;
private Class<?>[]                 parameterTypes;
private Class<?>[]                 exceptionTypes
private int                        modifiers;
// Generics and annotations support
private transient String           signature;
// Generic info repository; lazily initialized
private transient MethodRepository genericInfo;
private byte[]                     annotations;
private byte[]                     parameterAnnotations;
private byte[]                     annotationDefault;
private volatile MethodAccessor    methodAccessor;

咱們能夠經過getMethod得到對象的方法:

Object rcvr = "str";
try {
    Class<?>[] argTypes = new Class[] { };
    //其實這個參數沒有也不要緊,由於hashCode方法不須要參數
    Object[] args = null;

    Method hasMeth = rcvr.getClass().getMethod("hashCode", argTypes);
    Object ret = hasMeth.invoke(rcvr,args);
    System.out.println(ret);

} catch (IllegalArgumentException | NoSuchMethodException |
        SecurityException e) {
    e.printStackTrace();
} catch (IllegalAccessException | InvocationTargetException x) {
    x.printStackTrace();
}

若是要調用非公開方法,必須使用 getDeclaredMethod() 方法才能獲取非公開方法的引用,並且還要使用 setAccessible() 方法覆蓋 Java 的訪問控制子系統,而後才能執行:

public class MyCache {
    private void flush() {
        // 清除緩存……
    }
}

Class<?> clz = MyCache.class;
try {
    Object rcvr = clz.newInstance();
    Class<?>[] argTypes = new Class[]{};
    Object[] args = null;
    Method meth = clz.getDeclaredMethod("flush", argTypes);
    meth.setAccessible(true);
    meth.invoke(rcvr, args);
} catch (IllegalArgumentException | NoSuchMethodException |
        InstantiationException | SecurityException e) {
    e.printStackTrace();
} catch (IllegalAccessException | InvocationTargetException x) {
    x.printStackTrace();
}

反射的問題

Java 的反射 API 每每是處理動態加載代碼的惟一方式,不過 API 中有些讓人頭疼的地方,處理起來稍微有點困難:

  • 大量使用 Object[] 表示調用參數和其餘實例;
  • 大量使用 Class[] 表示類型;
  • 同名方法能夠重載,因此須要維護一個類型組成的數組,區分不一樣的方法;
  • 不能很好地表示基本類型——須要手動打包和拆包。

void 就是個明顯的問題——雖然有 void.class,但沒堅持用下去。Java 甚至不知道 void 是否是一種類型,並且反射 API 中的某些方法使用 null 代替 void。

這很難處理,並且容易出錯,尤爲是稍微有點冗長的數組句法,更容易出錯。

動態代理

Java反射的API中還提供了動態代理。動態代理是實現了一些接口的類(擴展 java.lang.reflect.Proxy 類)。這些類在運行時動態建立,並且會把全部調用都轉交給 InvocationHandler 對象處理:

InvocationHandler h = new InvocationHandler() {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
                                        throws javaThrowable {
    String name = method.getName();
    System.out.println("Called as: "+ name);
    switch (name) {
      case "isOpen":
        return false;
      case "close":
        return null;
    }

    return null;
  }
};

Channel c =
  (Channel) Proxy.newProxyInstance(Channel.class.getClassLoader(),
                            new Class[] { Channel.class }, h);

c.isOpen();
c.close();

代理能夠用做測試的替身對象(尤爲是測試使用模擬方式實現的對象)。

代理的另外一個做用是提供接口的部分實現,或者修飾或控制委託對象的某些方面:

public class RememberingList implements InvocationHandler {
  private final List<String> proxied = new ArrayList<>();

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
                         throws Throwable {
    String name = method.getName();
    switch (name) {
      case "clear":
        return null;
      case "remove":
      case "removeAll":
        return false;
    }

    return method.invoke(proxied, args);
  }
}

RememberingList hList = new RememberingList();

List<String> l =
  (List<String>) Proxy.newProxyInstance(List.class.getClassLoader(),
                                        new Class[] { List.class },
                                        hList);
l.add("cat");
l.add("bunny");
l.clear();
System.out.println(l);

Java7中的方法句柄

Java7中提供了方法句柄,比起「傳統」的反射機制。更爲好用,並且性能更好。

以以前的反射hashCode爲例

Object rcvr = "str";
       try {
           MethodType mt = MethodType.methodType(int.class);
           MethodHandles.Lookup l = MethodHandles.lookup();
           MethodHandle hashMeth = l.findVirtual(rcvr.getClass(), "hashCode", mt);

           int result;
           try {
               result = (int) hashMeth.invoke(rcvr);
               System.out.println(result);
           } catch (Throwable t) {
               t.printStackTrace();
           }
       } catch (IllegalArgumentException |
               NoSuchMethodException | SecurityException e) {
           e.printStackTrace();
       } catch (IllegalAccessException x) {
           x.printStackTrace();
       }
  • 其中MethodType.methodType(int.class);指定了方法的返回類型,其實不止如此。methodType還能夠填入其函數參數
  • 經過MethodHandles.Lookup能夠得到當前執行方法的上下文對象,在這個對象上能夠調用幾個方法(方法名都以 find 開頭),查找須要的方法,包括findVirtual()findConstructor()` 和 findStatic()

    • 反射 API 和方法句柄 API 之間一個重大的區別是處理訪問控制的方式。Lookup 對象只會返回在建立這個對象的上下文中能夠訪問的方法——沒有任何方式能破壞這個規則(不像反射 API 可使用 setAccessible() 方法調整訪問控制)
  • 經過 Lookup 對象能夠爲任何能訪問的方法生成方法句柄,還能訪問方法沒法訪問的字段。在 Lookup 對象上調用 findGetter()findSetter() 方法,分別能夠生成讀取字段和更新字段的方法句柄。
  • 以後經過MethodHandles.Lookup.findVirtual()得到了方法句柄(MethodHandle)
  • 方法句柄表示調用方法的能力。方法句柄對象是強類型的,會盡可能保證類型安全。方法句柄都是 java.lang.invoke.MethodHandle 類的子類實例,JVM 會使用特殊的方式處理這個類。
  • 通常來講,invoke() 方法會調用 asType() 方法轉換參數。轉換的規則以下:

    • 若是須要,打包基本類型的參數。
    • 若是須要,拆包打包好的基本類型參數。
    • 若是須要,放大轉換基本類型的參數。
    • 會把 void 返回類型修改成 0 或 null,具體是哪一個取決於期待的返回值是基本類型仍是引用類型。
    • 無論靜態類型是什麼,都能傳入 null。

小結

方法句柄提供的動態編程功能和反射同樣,但處理方式更清晰明瞭。並且,方法句柄能在 JVM 的低層執行模型中很好地運轉,所以,性能比反射好得多。

相關文章
相關標籤/搜索