Java基礎之類型信息(一)

我們都知道對於面向對象的語言,一般都會在編譯期執行類型檢查。那如果我們要在運行期間如何對數據類型進行提取並識別那,在Java裏面有兩種方式可以做到這一點。其一,利用傳統的RTTI,它假定我們在編譯時就已經知道所有類型。另一種是“反射”機制,它允許我們在運行時發現和使用類的信息。

RTTI(運行時類型識別)

RTTI的一般形式

我們先來看一段代碼

abstract class Shape {
  void draw() { System.out.println(this + ".draw()"); }
  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 class Shapes {
  public static void main(String[] args) {
    List<Shape> shapeList = Arrays.asList(
      new Circle(), new Square(), new Triangle()
    );
    for(Shape shape : shapeList)
      shape.draw();
  }
}

在這個例子中,當Shape對象存入到List<Shape>的數組時會向上轉型。但是向上轉型爲Shape的時候會丟失具體類型。對於數組來說,它們都是Shape類型,這裏要特殊說明一下,當數組的元素被取出的過程中實際的過程是它會把所有的事物當做Object持有,之後會發生結果轉型,轉回Shape的過程。這也正是RTTI最經典的使用方式

同時,也說明一點,在這裏RTTI類型轉換並沒有徹底:Object被轉型爲Shape,而不是轉型爲Circle、Square或者Triangle。之後Shape具體執行哪種代碼,就交給多態機制操作

這也是我們代碼編寫過程中慣用的方法,儘可能少的瞭解對象的具體類型,只與一個通用的窗口打交道。這種寫法也便於理解項目同時,也更容易維護。

RTTI實現機制

RTTI功能的實現是由Class對象來完成的,它包含了類的所有相關信息,而這個對象是在編寫並編譯一個新的Java類時產生的。而且這個對象只有在其使用時纔會被加載。那我們該如何獲取這個Class對象那,其實他和普通對象一樣,通過以下三種方式來獲取,

方式1:通過Object類的getObject()方法 

Person p = new Person();
Class c = p.getClass();

方式2: 通過 類名.class 獲取到字節碼文件對象(任意數據類型都具備一個class靜態屬性,看上去要比第一種方式簡單)

Class c2 = Person.class;

方式3: 通過Class類中的方法(將類名作爲字符串傳遞給Class類中的靜態方法forName即可)

Class c3 = Class.forName("Person");

這三種方式中,一般會選擇第三種方式,因爲在你獲取恰當的Class對象的引用時,不需要像前兩種方式需要擁有該類型對象

當你獲取到Class對象之後,你就可以使用它來獲取你想要的瞭解的類型的所有信息,包括創建對象。

Class是如何創建對象的,大家先看以下代碼:

import static net.mindview.util.Print.*;

interface HasBatteries {}
interface Waterproof {}
interface Shoots {}

class Toy {
  // Comment out the following default constructor
  // to see NoSuchMethodError from (*1*)
  Toy() {}
  Toy(int i) {}
}

class FancyToy extends Toy
implements HasBatteries, Waterproof, Shoots {
  FancyToy() { super(1); }
}

public class ToyTest {
  static void printInfo(Class cc) {
    print("Class name: " + cc.getName() +
      " is interface? [" + cc.isInterface() + "]");
    print("Simple name: " + cc.getSimpleName());
    print("Canonical name : " + cc.getCanonicalName());
  }
  public static void main(String[] args) {
    Class c = null;
    try {
      c = Class.forName("typeinfo.toys.FancyToy");
    } catch(ClassNotFoundException e) {
      print("Can't find FancyToy");
      System.exit(1);
    }
    printInfo(c);	
    for(Class face : c.getInterfaces())
      printInfo(face);
    Class up = c.getSuperclass();
    Object obj = null;
    try {
      // Requires default constructor:
      obj = up.newInstance();
    } catch(InstantiationException e) {
      print("Cannot instantiate");
      System.exit(1);
    } catch(IllegalAccessException e) {
      print("Cannot access");
      System.exit(1);
    }
    printInfo(obj.getClass());
  }
}

可以通過newInstance()方法來創建對象,該方法是實現“虛擬構造器”的一種途徑,虛擬構造器允許你聲明:“我不知道你的確切類型,但是無論如何要正確地創建”。在這段代碼中,up僅僅只是一個Class引用,但這個引用通過該方法可以成功的指向Toy對象。如果在實際生活中,應用這個方法,你需要很清楚的知道這個Class對象到底是什麼,並能夠執行什麼轉型。另外,在對象正確創建的過程中,必須是使用的默認構造器,不能使用其他構造器。這是該方法出現的缺點。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章