舉例講解Java的RTTI運行時類型識別機制_java

來源:互聯網
上載者:User

1、RTTI:
運行時類型資訊可以讓你在程式運行時發現和使用類型資訊。

在Java中運行時識別對象和類的資訊有兩種方式:傳統的RTTI,以及反射。下面就來說下RTTI。

RTTI:在運行時,識別一個對象的類型。但是這個類型在編譯時間必須已知。

下面通過一個例子來看下RTTI的使用。這裡涉及到了多態的概念:讓代碼只操作基類的引用,而實際上調用具體的子類的方法,通常會建立一個具體的對象(Circle,Square,或者Triangle,見下例),把它向上轉型為Shape(忽略了對象的具體類型),並在後面的程式中使用匿名(即不知道具體類型)的Shape引用:

abstract class Shape {  // this 調用當前類的toString()方法,返回實際的內容  void draw(){ System.out.println(this + "draw()"); }  // 聲明 toString()為abstract類型,強制整合在重寫該方法  abstract public String toString();}class Circle extends Shape {  public String toString(){ return "Circle"; }}class Square extends Shape {  public String toString(){ return "Square"; }}class Triangle extends Shape {  public String toString(){ return "Triangle"; }}public static void main(String[] args){  // 把Shape對象放入List<Shape>的數組的時候會向上轉型為Shape,從而丟失了具體的類型資訊  List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());  // 從數組中取出時,這種容器,實際上所有的元素都當成Object持有,會自動將結果轉型為Shape,這就是RTTI的基本的使用。  for(Shape shape : shapeList){    shape.draw();  }}

輸出結果為:

Circledraw()Squaredraw()Triangledraw()

存入數組的時候,會自動向上轉型為Shape,丟失了具體的類型,當從數組中取出的時候,(List容器將所有的事物都當做Object持有),會自動將結果轉型回Shape,這就是RTTI的基本用法。Java中所有的類型轉換都是在運行時進行正確性檢查的,也就是RTTI:在運行時,識別一個對象的類型。

上面的轉型並不徹底,數組的元素取出時又Object轉型為Shape,而不是具體的類型,編譯時間這是由容器和Java泛型系統來確保這一點的,而在運行時時有類型轉換操作來確保這一點的。

而能夠通過Shape對象執行到子類的具體代碼就是又多態來決定的了,具體看Shape引用所指向的具體對象。

另外,使用RTTI,可以查詢某個Shape引用所指向的對象的確切類型,然後選擇性的執行子類的方法。

2、Class對象:
要瞭解RTTI在Java中的工作原理,必須知道類型資訊在運行時是如何表示的,這裡是由Class這個特殊對象完成的。

Class對象是用來建立類的所有的“常規”對象的。Java使用Class對象來執行其RTTI。

每當編譯一個新類,就會產生一個Class對象(.class檔案)。運行這個程式的JVM將使用“類載入器”這個子系統。

類載入器子系統:包含一條類載入器鏈,但只有一個原生類載入器,它是JVM實現的一部分。原生類載入器載入可信類,包括Java API類,通常是從本地磁碟載入的。當需要以某種特定的方式載入類,以支援Web伺服器應用,可以掛接額外的類載入器。

2.1、載入類的時機:
當程式建立第一個對類的靜態成員的引用時,就會載入這個類。這證明其實構造器也是類的靜態方法,當使用new操作符建立類的新對象也會當做對類的靜態成員的引用。

可見Java程式時動態載入的,按需載入。需要用到Class時,類載入器首先會檢查這個類的Class對象是否已經載入,如果尚未載入,預設的類載入器就會根據類名尋找到.class檔案。接下來是驗證階段:載入時,它們會接受驗證,以確保其沒有被破壞,並且不包含不良Java代碼。

2.2、Class相關方法,newInstance()
下面通過一個例子示範Class對象的載入:

class A {  // 靜態程式碼程式庫,在第一次被載入時執行,通過列印資訊知道該類什麼時候被載入  static { System.out.println("Loading A"); }}class B {  static { System.out.println("Loading B"); }}class C {  static { System.out.println("Loading C"); }}public class Load {  public static void main(String[] args){    System.out.println("execute main...");    new A();    System.out.println("after new A");    try {      Class.forName("com.itzhai.test.type.B");    } catch (ClassNotFoundException e) {      System.out.println("cloud not find class B");    }    System.out.println("after Class.forName B");    new C();    System.out.println("after new C");  }}

輸出結果為:

execute main...Loading Aafter new ALoading Bafter Class.forName BLoading Cafter new C

可見,Class對象在需要的時候才被載入,注意到這裡的Class.forName()方法:

forName()方法是取得Class對象的引用的一種方法,通過這個方法獲得恰當的Class對象的引用,就可以在運行時使用類型資訊了。

如果你已經有了一個感興趣的類型的對象,則可以通過跟類Object提供的getClass()方法來獲得Class引用。

下面是一段Class的使用的代碼:

interface X{}interface Y{}interface Z{}class Letter {  Letter(){};  Letter(int i){};}class NewLetter extends Letter implements X, Y, Z{  NewLetter(){ super(1); };}public class ClassTest {  /**   * 列印類型資訊   * @param c   */  static void printInfo(Class c){    // getName()獲得全限定的類名    System.out.println("Class name: " + c.getName() + " is interface? " + c.isInterface());    // 獲得不包含包名的類名    System.out.println("Simple name: " + c.getSimpleName());    // 獲得全限定類名    System.out.println("Canonical name: " + c.getCanonicalName());  }  public static void main(String[] args){    Class c = null;    try {      // 獲得Class引用      c = Class.forName("com.itzhai.test.type.NewLetter");    } catch (ClassNotFoundException e) {      System.out.println("Can not find com.itzhai.test.type.NewLetter");      System.exit(1);    }    // 列印介面類型資訊    for(Class face : c.getInterfaces()){      printInfo(face);    }    // 擷取超類Class引用    Class up = c.getSuperclass();    Object obj = null;    try {      // 通過newInstance()方法建立Class的執行個體      obj = up.newInstance();    } catch (InstantiationException e) {      System.out.println("Can not instantiate");    } catch (IllegalAccessException e) {      System.out.println("Can not access");    }    // 列印超類類型資訊    printInfo(obj.getClass());  }}

輸出為:

Class name: com.itzhai.test.type.X is interface? trueSimple name: XCanonical name: com.itzhai.test.type.XClass name: com.itzhai.test.type.Y is interface? trueSimple name: YCanonical name: com.itzhai.test.type.YClass name: com.itzhai.test.type.Z is interface? trueSimple name: ZCanonical name: com.itzhai.test.type.ZClass name: com.itzhai.test.type.Letter is interface? falseSimple name: LetterCanonical name: com.itzhai.test.type.Letter

注意,在傳遞給forName()的字串必須使用全限定名(包括包名)。

通過printInfo裡面使用到的方法,你可以在運行時發現一個對象完整的類繼承結構。

通過使用Class的newInstance()方法是實現“虛擬構造器”的一種途徑,用來建立Class的執行個體,得到的是Object引用,但是引用時指向Letter對象。使用newInstance()來建立的類,必須帶有預設的構造器。(而通過反射API,可以用任意的構造器來動態建立類的對象)。

2.3、類字面常量:
除了使用getName()方法,Java還提供了另一種方法來產生對Class對象的引用,即使用類字面常量:

NewLetter.class;

此方法簡單安全,編譯時間就受到檢查,更高效。不僅可用於普通類,也可以用於介面,數組以及基礎資料型別 (Elementary Data Type)。另外,對於基礎資料型別 (Elementary Data Type)的封裝器類,還有一個標準欄位TYPE,TYPE欄位是一個引用,執行對應的基礎資料型別 (Elementary Data Type)的Class對象。為了統一,建議都使用.class這種形式。

2.4、使用.class與使用getName()方法建立對象引用的區別:
使用.class建立時,不會自動的初始化Class對象。建立步驟如下:

(1)載入 由類載入器執行:尋找位元組碼(通常是在classpath指定的路徑中尋找,但並非必須的),然後從這些位元組碼中建立一個Class對象。

(2)連結 將驗證類中的位元組碼,為靜態域分配儲存空間,如果需要,將會解析這個類建立的對其他類的所有的引用。

(3)初始化 如果該類具有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。

初始化被延遲到了對靜態方法(構造器隱式的是靜態)或者非常數靜態域進行首次引用時才執行的:

class Data1{  static final int a = 1;  static final double b = Math.random();  static {    System.out.println("init Data1...");  }}class Data2{  static int a = 12;  static {    System.out.println("init Data2...");  }}class Data3{  static int a = 23;  static {    System.out.println("init Data3...");  }}public class ClassTest2 {  public static void main(String[] args){    System.out.println("Data1.class: ");    Class data1 = Data1.class;    System.out.println(Data1.a); // 沒有初始化Data1    System.out.println(Data1.b); // 初始化了Data1    System.out.println(Data2.a); // 初始化了Data2    try {      Class data3 = Class.forName("com.itzhai.test.type.Data3"); // 初始化了Data3    } catch (ClassNotFoundException e) {      System.out.println("can not found com.itzhai.test.type.Data3...");    }    System.out.println(Data3.a);  }}

輸出的結果為:

Data1.class: 1init Data1...0.26771085109184534init Data2...12init Data3...23

初始化有效實現了儘可能的“惰性”。

2.5、下面是判斷是否執行初始化的一些情況:
(1)class文法獲得對類的引用不會引發初始化;

(2)Class.forName()產生了Class引用,立即進行了初始化;

(3)如果一個static final值是“編譯器常量”,那麼這個值不需要對類進行初始化就可以被讀取;

(4)如果只是把一個網域設定為static final還不足以確保這種行為,例如上面的:

static final double b = Math.random();

(5)如果一個static域bushifinal的,那麼在對它訪問時,總是要先進性連結和初始化;

2.6、泛化的Class引用:
Class參考資料表示的是它所指向的對象的確切類型,而該對象便是Class類的一個對象。在JavaSE5中,可以通過泛型對Class引用所指向的Class對象進行限定,並且可以讓編譯器強制執行額外的類型檢查:

Class intCls = int.class;// 使用泛型限定Class指向的引用Class<Integer> genIntCls = int.class;// 沒有使用泛型的Clas可以重新賦值為指向任何其他的Class對象intCls = double.class;// 下面的編譯會出錯// genIntCls = double.class;

2.6.1、使用萬用字元?放鬆泛型的限定:

Class<?> intCls = int.class;intCls = String.class;

在JavaSE5中,Class<?>優於平凡的Class,更建議使用Class<?>,即便它們是等價的,因為Class<?>的好處是它表示你並非是碰巧或者疏忽,而是使用了一個非具體的類引用。

為了限定Class的引用為某種類型,或者該類型的子類型可以將萬用字元與extends一起使用,建立一個範圍:

Class<? extends Number> num = int.class;// num的引用範圍為Number及其子類,所以可以按照如下賦值num = double.class;num = Number.class;

2.6.2、泛型下的newInstance()方法:
使用了泛型後的Class,調用newInstance()返回的對象是確切類型的,但是當你使用getSuperclass()擷取泛型對應的超類的時候真正的類型會有一些限制:編譯器在編譯期就知道了超類的類型,但是,通過這個擷取到的超類引用的newInstance()方法返回的不是精確類型,而是Object:

Dog dog = dogCls.newInstance();abstract class Animal {}class Dog extends Animal{}// 下面的寫法是錯誤的,只能返回 Class<? super Dog>類型// Class<Animal> animalCls = dogCls.getSuperclass(); Class<? super Dog> animalCls = dogCls.getSuperclass();// 通過擷取的超類引用,只能建立返回Object類型的對象Object obj = animalCls.newInstance();

2.6.3、新的轉型文法:cast()方法
直接看下代碼:

Animal animal = new Dog();Class<Dog> dogCls = Dog.class;Dog dog = dogCls.cast(animal);// 或者直接使用下面的轉型方法dog = (Dog)animal;

可以發現,使用cast()方法的做了額外的工作,這種轉換方法可以用在一下的情況中:在編寫泛型帶的時候,如果儲存了Class引用,並希望通過這個Class引用來執行轉型,就可以使用cast()方法。

3、類型檢查 instanceof
3.1、類型轉換前先做檢查
編譯器允許你自由的做向上轉型的賦值操作,而不需要任何顯示的轉型操作,就好像給超類的引用賦值那樣。

然而如果不使用顯示的類型轉換,編譯器就不允許你執行向下轉換賦值,這個時候我們不妨先來檢查一下對象是不是某個特定類型的執行個體,使用到了關鍵字 instanceof:

if(x instanceof Dog)  ((Dog) x).bark();

3.2、RTTI的形式:
所以,到目前為止,我們知道RTTI的形式包括:

(1)傳統的類型轉換 (Shape)

(2)代表對象的類型的Class對象

(3)關鍵字instanceof

3.3、動態instanceof方法:
Class.isInstance方法提供給了一種動態測試對象的途徑。

下面示範下instanceof和Class.isInstance的用法:

Attribute:

public interface Attribute {}

Shape:

/** * 建立一個抽象類別 */public abstract class Shape{  // this調用了當前類的toString方法獲得資訊  public void draw() { System.out.println(this + ".draw()"); }  // 聲明toString()方法為abstract,從而強制繼承者需要重寫該方法。  abstract public String toString();}

Circle:

public class Circle extends Shape implements Attribute{  public String toString(){ return "Circle"; }}

Square:

public class Square extends Shape{  public String toString(){ return "Square"; }}

Triangle:

public class Triangle extends Shape{  public String toString(){ return "Triangle"; }}

類型檢查:

// instanceOfCircle c = new Circle();// 判斷是否超類的執行個體System.out.format("Using instanceof: %s is a shape? %b\n",     c.toString(), c instanceof Shape);// 判斷是否Circle的執行個體System.out.format("Using instanceof: %s is a circle? %b\n",     c.toString(), c instanceof Circle);// 判斷是否超類的執行個體System.out.format("Using Class.isInstance: %s is a shape? %b\n",     c.toString(), Shape.class.isInstance(c));// 判斷是否介面的執行個體System.out.format("Using Class.isInstance: %s is a Attribute? %b\n",     c.toString(), Attribute.class.isInstance(c));

可以發現,instanceof或者Class.isInstance方法判斷了是否繼承體系的執行個體,即除了判斷本身,還判斷是否超類或介面的執行個體。

下面示範下使用動態Class.instance的用法:

首先建立一個抽象的形狀產生器類:

public abstract class ShapeCreator {  private Random rand = new Random(10);  // 返回一個物件類型數組,由實作類別提供,後面會看到兩種實現形式,基於forName的和基於類字面常量的.class  public abstract List<Class<? extends Shape>> types();  // 隨機產生一個物件類型數組中的類型對象執行個體  public Shape randomShape(){    int n = rand.nextInt(types().size());    try {      return types().get(n).newInstance();    } catch (InstantiationException e) {      e.printStackTrace();      return null;    } catch (IllegalAccessException e) {      e.printStackTrace();      return null;    }  }  // 產生一個隨機數組  public Shape[] createArray(int size){    Shape[] result = new Shape[size];    for(int i=0; i<size; i++){      result[i] = randomShape();    }    return result;  }  // 產生一個隨機數組,泛型的ArrayList  public ArrayList<Shape> arrayList(int size){    ArrayList<Shape> result = new ArrayList<Shape>();    Collections.addAll(result, createArray(size));    return result;  }}

接下來編寫一個該抽象類別的實現:

/** * forName的產生器實現 * @author arthinking * */public class ForNameCreator extends ShapeCreator{  private static List<Class<? extends Shape>> types =       new ArrayList<Class<? extends Shape>>();  private static String[] typeNames = {    "com.itzhai.javanote.entity.Circle",    "com.itzhai.javanote.entity.Square",    "com.itzhai.javanote.entity.Triangle"  };  @SuppressWarnings("unused")  private static void loader(){    for(String name : typeNames){      try {        types.add((Class<? extends Shape>)Class.forName(name));      } catch (ClassNotFoundException e) {        e.printStackTrace();      }    }  }  // 初始化載入所需的類型數組  static {    loader();  }  public List<Class<? extends Shape>> types() {    return types;  }}

最後寫一個統計形狀個數的類,裡面用到了instanceof:

public class ShapeCount {  static class ShapeCounter extends HashMap<String, Integer>{    public void count(String type){      Integer quantity = get(type);      if(quantity == null){        put(type, 1);      } else {        put(type, quantity + 1);      }    }  }  // 示範通過instanceof關鍵字統計物件類型  public static void countShapes(ShapeCreator creator){    ShapeCounter counter = new ShapeCounter();    for(Shape shape : creator.createArray(20)){      if(shape instanceof Circle)        counter.count("Circle");      if(shape instanceof Square)        counter.count("Square");      if(shape instanceof Triangle){        counter.count("Triangle");      }    }    System.out.println(counter);  }  public static void main(String[] args){    countShapes(new ForNameCreator());  }}

改寫一下抽象類別的實現,重新用類字面常量實現:

/** * 字面量的產生器實現 */public class LiteralCreator extends ShapeCreator{  public static final List<Class<? extends Shape>> allType =       Collections.unmodifiableList(Arrays.asList(Circle.class, Triangle.class, Square.class));  public List<Class<? extends Shape>> types(){    return allType;  }  public static void main(String[] args){    System.out.println(allType);  }}

現在使用Class.instance統計形狀的個數如下:

/** * 通過使用Class.instanceof動態測試對象,移除掉原來的ShapeCount中單調的instanceof語句 * */public class ShapeCount2 {  private static final List<Class<? extends Shape>> shapeTypes = LiteralCreator.allType;  static class ShapeCounter extends HashMap<String, Integer>{    public void count(String type){      Integer quantity = get(type);      if(quantity == null){        put(type, 1);      } else {        put(type, quantity + 1);      }    }  }  // 示範通過Class.isInstance()統計物件類型  public static void countShapes(ShapeCreator creator){    ShapeCounter counter = new ShapeCounter();    for(Shape shape : creator.createArray(20)){      for(Class<? extends Shape> cls : shapeTypes){        if(cls.isInstance(shape)){          counter.count(cls.getSimpleName());        }      }    }    System.out.println(counter);  }  public static void main(String[] args){    countShapes(new ForNameCreator());  }}

現在產生器有了兩種實現,我們在這裡可以添加一層外觀,設定預設的實現方式:

/** * 現在產生器有了兩種實現,我們在這裡添加一層外觀,設定預設的實現方式 */public class Shapes {  public static final ShapeCreator creator =      new LiteralCreator();  public static Shape randomShape(){    return creator.randomShape();  }  public static Shape[] createArray(int size){    return creator.createArray(size);  }  public static ArrayList<Shape> arrayList(int size){    return creator.arrayList(size);  }}

3.4、instanceof與Class的等價性:
instanceof和isInstance()產生的結果完全一樣,保持了類型的概念,判斷是否一個類或者是這個類的衍生類別。

equals()與==也是一樣的,而使用這個比較實際的Class對象,就沒有考慮繼承。

System.out.println(new Circle() instanceof Circle); // trueSystem.out.println(Shape.class.isInstance(new Circle())); // trueSystem.out.println((new Circle()).getClass() == Circle.class); // trueSystem.out.println((new Circle().getClass()).equals(Shape.class)); // false

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.