首頁 > Java > java教程 > 主體

Java中的RTTI與反射機制的詳解

黄舟
發布: 2017-09-20 09:59:53
原創
1249 人瀏覽過

這篇文章主要涉及了Java的RTTI和反射機製程式碼分析的相關內容,在介紹運行時類型識別的同時,又向大家展示了其實例以及什麼時候會用到反射機制,內容豐富,需要的朋友可以參考下。

RTTI,即Run-Time Type Identification,執行階段類型辨識。運行時類型識別是Java中非常有用的機制,在Java運行時,RTTI維護類別的相關資訊。 RTTI能在執行時就能夠自動辨識每個編譯時已知的型別。

很多時候需要進行上轉型,例如Base類別衍生出Derived類,但是現有的方法只需要將Base物件作為參數,實際傳入的則是其衍生類別的參考。那麼RTTI就在此時起到了作用,例如透過RTTI能辨識出Derive類是Base的衍生類,這樣就能夠向上轉型為Derived。類似的,在以介面作為參數時,向上轉型較為常用,RTTI此時能夠判斷是否可以進行上轉型。

而這些類型資訊是透過Class物件(java.lang.Class)的特殊物件完成的,它包含跟類別相關的資訊。每當編寫並編譯一個類別時就會產生一個.class文件,保存著Class對象,運行這個程式的Java虛擬機(JVM)將使用被稱為類別載入器(Class Loader)的子系統。而類別載入器並非在程式運作之前就載入所有的Class對象,如果尚未加載,預設的類別載入器就會根據類別名稱來尋找.class檔案(例如,某個附加類別載入器可能會在資料庫中尋找字節碼),在這個類別的字節碼被載入時接受驗證,以確保沒有被破壞並且不包含不良Java代碼。這也是Java中的型別安全機制之一。一旦某個類別的Class物件被載入內存,就可以建立該類別的所有物件。


package typeinfo;
class Base {
  static { System.out.println("加载Base类"); }
}
class Derived extends Base { 
  static { System.out.println("加载Derived类");}
}
public class Test {
  static void printerInfo(Class c) {
    System.out.println("类名: " + c.getName() +
      "是否接口? [" + c.isInterface() + "]");
  }
  public static void main(String[] args) {
    Class c = null;
    try {
      c = Class.forName("typeinfo.Derived");
    } catch (ClassNotFoundException e) {
      System.out.println("找不到Base类");
      System.exit(1);
    }
    printerInfo(c);
    Class up = c.getSuperclass(); // 取得c对象的基类
    Object obj = null;
    try {
      obj = up.newInstance();
    } catch (InstantiationException e) {
      System.out.println("不能实例化");
      System.exit(1);
    } catch (IllegalAccessException e) {
      System.out.println("不能访问");
      System.exit(1);
    }
    printerInfo(obj.getClass());
  } /* 输出:
  加载Base类
  加载Derived类
  类名: typeinfo.Derived是否接口? [false]
  类名: typeinfo.Base是否接口? [false]
  */
}
登入後複製

上述程式碼中,forName方法是靜態方法,參數是類別名,用來找出是否存在該類,如果找到則回傳一個Class引用,否則會拋出ClassNotFoundException例外。

如果類別不是在預設資料夾下,而是在某個套件下,前面的套件名稱需要帶上,例如這裡的typeinfo.Derived。

可以透過getSuperclass方法傳回基底類別對應的Class物件。使用newInstance方法可以按預設構造建立一個實例對象,在不能實例化和不能存取時分別拋出。會拋出InstantiationException和IllegalAccessException異常。

Java也提供了一種方法來產生對Class物件的引用,即類別字面常數。對上述程式來說,up等價於Base.class。

對於基本資料型別的包裝類別來說,char.class等價於Character.TYPE,int.class等價於Integer.TYPE。其餘的ab.class等價於Ab.TYPE。 (如void.class等價於Void.TYP)。另外,Java SE5開始int.class和Integer.class也是一回事。

泛化的Class引用,見下面程式碼


    Class intClass = int.class;
    Class<Integer> genericIntClass = int.class;
    genericIntClass = Integer.class; // 等价
    intClass = double.class; // ok
    // genericIntClass = double.class; // Illegal!
登入後複製

Class物件的引用指定了Integer對象,所以不能將引用指向double.class。為了放鬆限制可以使用通配符?,即Class,效果跟Class是一樣的,但是代碼更為優雅,使用Class表示你並非是碰巧或疏忽才使用一個非具體的類引用。同時,可以限制繼承的類,範例如下


class Base {}
class Derived extends Base {}
class Base2 {}
public class Test {
  public static void main(String[] args) {
    Class<? extends Base> cc = Derived.class; // ok
    // cc = Base2.class; // Illegal
  } 
}
登入後複製

向Class引用添加泛型語法的原因只是為了提供編譯期類型檢查,以便在編譯時就能發現類型錯誤。

總結下來,我們已知的RTTI形式包括:

1、傳統的類型轉換,由RTTI保證類型轉換的正確性,如果執行一個錯誤的類型轉換,就會拋出ClassCastException異常;

2、代表對象的類型的Class對象,透過查詢Class對象(即呼叫Class類別的方法)可以取得運行時所需的訊息。

在C++中經典的型別轉換並不使用RTTI,這點具體見C++的RTTI部分。 (說句題外話,以前學C++時看到RTTI這章只是隨便掃了眼,現在才記起來dynamic_cast什麼的都是為了類型安全而特地添加的,C++在安全方面可以提供選擇性,就像Java的StringBuilder和StringBuffer,安全和效率不可兼得?

而Java中RTTI還有第3種形式,就是關鍵字instanceof,回傳一個布林值,告訴物件是不是某個特定類型的範例,請參閱下列程式碼。


class Base {}
class Derived extends Base {}
public class Test {
  public static void main(String[] args) {
    Derived derived = new Derived();
    System.out.println(derived instanceof Base); // 输出true
  } 
}
登入後複製

利用instanceof 可以判斷某些類型,例如基底類別Shape衍生出各種類別(Circle、Rectangle等),現在某方法要為所有Circle上色,而輸入參數時一堆Shape對象,此時就可以用instandof判斷該Shape對像是不是Circle對象。

RTTI可以识别程序空间的所有类,但是有时候需要从磁盘文件或网络文件中读取一串字节码,并且被告知这些字节代表一个类,就需要用到反射机制。

比如在IDE中创建图形化程序时会使用到一些控件,只需要从本地的控件对应class文件中读取即可,然后再主动修改这些控件的属性。(题外话:大概.net组件就是这样的?学C#时总听到反射,但总没感觉用过,前几天做.net项目的同学也跟我说他从来都没用过委托和事件……)

Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含Field、Method和Constructor类(每个类都实现了Member接口),这些类型的对象都是JVM在运行时创建的,用以表示未知类里对应成员。

这样就可以用Constructor创建未知对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke方法调用与Method对象关联的字段,等等。


// 使用反射展示类的所有方法, 即使方法是在基类中定义的
package typeinfo;
// Print类的print方法等价于System.Out.Println,方便减少代码量
import static xyz.util.Print.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
// {Args: typeinfo.ShowMethods}
public class ShowMethods {
  private static String usage = 
    "usage:\n" +
    "ShowMethods qualified.class.name\n" +
    "To show all methods in class or:\n" +
    "ShowMethods qualified.class.name word\n" +
    "To search for methods involving &#39;word&#39;";
  // 去掉类名前面的包名
  private static Pattern p = Pattern.compile("\\w+\\.");
  public static void main(String[] args) {
    if (args.length < 1) {
      print(usage);
      System.exit(0);
    }
    int lines = 0;
    try {
      Class<?> c = Class.forName(args[0]);
      // 反射获得对象c所属类的方法
      Method[] methods = c.getMethods();
      // 反射获得对象c所属类的构造
      Constructor[] ctors = c.getConstructors();
      if (args.length == 1) {
        for (Method method : methods)
          print(p.matcher(method.toString()).replaceAll(""));
        for (Constructor ctor : ctors)
          print(p.matcher(ctor.toString()).replaceAll(""));
      }
    } catch (ClassNotFoundException e) {
      print("No such class: " + e);
    }
  } /*
  public static void main(String[])
  public final void wait() throws InterruptedException
  public final void wait(long,int) throws InterruptedException
  public final native void wait(long) throws InterruptedException
  public boolean equals(Object)
  public String toString()
  public native int hashCode()
  public final native Class getClass()
  public final native void notify()
  public final native void notifyAll()
  public ShowMethods()
  */
}
登入後複製

简单来说,反射机制就是识别未知类型的对象。反射常用于动态代理中。举例如下:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class DynamicProxyHandler implements InvocationHandler {
  private Object proxied; // 代理对象
  public DynamicProxyHandler(Object proxied) {
    // TODO Auto-generated constructor stub
    this.proxied = proxied;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // TODO Auto-generated method stub
    System.out.println("代理类: " + proxy.getClass() + "\n"
        + "代理方法: " + method + "\n"
        + "参数: " + args);
    if (args != null)
      for (Object arg : args)
        System.out.println(" " + arg);
    return method.invoke(proxied, args);
  }
}
interface Interface { void doSomething(); }

class RealObject implements Interface {
  
  @Override
  public void doSomething() {
    // TODO Auto-generated method stub
    System.out.println("doSomething");
  }
}
public class DynamicProxyDemo {
  public static void consumer(Interface iface) {
    iface.doSomething();
  }
  public static void main(String[] args) {
    RealObject realObject = new RealObject();
    // 使用动态代理
    Interface proxy = (Interface)Proxy.newProxyInstance(
        Interface.class.getClassLoader(),
        new Class[] { Interface.class }, 
        new DynamicProxyHandler(realObject));
    consumer(proxy);
  } /* 输出:
  代理类: class $Proxy0
  代理方法: public abstract void Interface.doSomething()
  参数: null
  doSomething
  */
}
登入後複製

代理是基本的设计模式之一,即用代理类为被代理类提供额外的或不同的操作。而动态代理则需要一个类加载器,就像Java实现RTTI时需要类加载器加载类的信息,这样就可以知道类的相关信息。

关键方法是:

Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException

传入三个参数:代理接口的加载器(通过Class对象的getClassLoader方法获取),代理的方法接口,代理对象

前两个参数很好理解,就是要代理的方法所属的接口对应的Class对象(主语)的加载器和Class对象本身,主要是参数3,要设计一个实现InvocationHandler接口的类,作为代理对象,一般命名以Handler结尾,Handler翻译为处理者,很形象,就是代替原对象进行处理的处理者(即代理),在程序设计中经常被翻译成“句柄”。

这个类通过传入代理对象来构造,比如这里传入的是Object对象。然后必须覆盖invoke方法。

通过最后输出和invoke方法的具体实现可以发现,return method.invoke(proxied, args);是相当于原对象调用该方法(类似C++的回调函数?)

由于有类加载器,所以代理对象可以知道原对象的具体类名、方法、参数,本示例在调用方法前就输出了这些。

实际应用中可能会针对类名而有所选择。比如接口中有好多个类,你可以选择性的对特定的类、方法、参数进行处理

比如 if(proxied instanceof RealObject) {} 或者 if(method.getName.equals("doSomething")) {}

总结

以上是Java中的RTTI與反射機制的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!