我們都知道對於面向對象的語言,一般都會在編譯期執行類型檢查。那如果我們要在運行期間如何對數據類型進行提取並識別那,在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對象到底是什麼,並能夠執行什麼轉型。另外,在對象正確創建的過程中,必須是使用的默認構造器,不能使用其他構造器。這是該方法出現的缺點。