1、RTTI:
運行時類型信息可以讓你在程序運行時發(fā)現(xiàn)和使用類型信息。
在Java中運行時識別對象和類的信息有兩種方式:傳統(tǒng)的RTTI,以及反射。下面就來說下RTTI。
RTTI:在運行時,識別一個對象的類型。但是這個類型在編譯時必須已知。
下面通過一個例子來看下RTTI的使用。這里涉及到了多態(tài)的概念:讓代碼只操作基類的引用,而實際上調(diào)用具體的子類的方法,通常會創(chuàng)建一個具體的對象(Circle,Square,或者Triangle,見下例),把它向上轉型為Shape(忽略了對象的具體類型),并在后面的程序中使用匿名(即不知道具體類型)的Shape引用:

abstract class Shape { // this 調(diào)用當前類的toString()方法,返回實際的內(nèi)容 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>的數(shù)組的時候會向上轉型為Shape,從而丟失了具體的類型信息 List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle()); // 從數(shù)組中取出時,這種容器,實際上所有的元素都當成Object持有,會自動將結果轉型為Shape,這就是RTTI的基本的使用。 for(Shape shape : shapeList){ shape.draw(); }}輸出結果為:
Circledraw()Squaredraw()Triangledraw()
存入數(shù)組的時候,會自動向上轉型為Shape,丟失了具體的類型,當從數(shù)組中取出的時候,(List容器將所有的事物都當做Object持有),會自動將結果轉型回Shape,這就是RTTI的基本用法。Java中所有的類型轉換都是在運行時進行正確性檢查的,也就是RTTI:在運行時,識別一個對象的類型。
上面的轉型并不徹底,數(shù)組的元素取出時又Object轉型為Shape,而不是具體的類型,編譯時這是由容器和Java泛型系統(tǒng)來確保這一點的,而在運行時時有類型轉換操作來確保這一點的。
而能夠通過Shape對象執(zhí)行到子類的具體代碼就是又多態(tài)來決定的了,具體看Shape引用所指向的具體對象。
另外,使用RTTI,可以查詢某個Shape引用所指向的對象的確切類型,然后選擇性的執(zhí)行子類的方法。
2、Class對象:
要了解RTTI在Java中的工作原理,必須知道類型信息在運行時是如何表示的,這里是由Class這個特殊對象完成的。
Class對象是用來創(chuàng)建類的所有的“常規(guī)”對象的。Java使用Class對象來執(zhí)行其RTTI。
每當編譯一個新類,就會產(chǎn)生一個Class對象(.class文件)。運行這個程序的JVM將使用“類加載器”這個子系統(tǒng)。
類加載器子系統(tǒng):包含一條類加載器鏈,但只有一個原生類加載器,它是JVM實現(xiàn)的一部分。原生類加載器加載可信類,包括Java API類,通常是從本地磁盤加載的。當需要以某種特定的方式加載類,以支持Web服務器應用,可以掛接額外的類加載器。
2.1、加載類的時機:
當程序創(chuàng)建第一個對類的靜態(tài)成員的引用時,就會加載這個類。這證明其實構造器也是類的靜態(tài)方法,當使用new操作符創(chuàng)建類的新對象也會當做對類的靜態(tài)成員的引用。
可見Java程序時動態(tài)加載的,按需加載。需要用到Class時,類加載器首先會檢查這個類的Class對象是否已經(jīng)加載,如果尚未加載,默認的類加載器就會根據(jù)類名查找到.class文件。接下來是驗證階段:加載時,它們會接受驗證,以確保其沒有被破壞,并且不包含不良Java代碼。
2.2、Class相關方法,newInstance()
下面通過一個例子演示Class對象的加載:
class A { // 靜態(tài)代碼庫,在第一次被加載時執(zhí)行,通過打印信息知道該類什么時候被加載 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對象的引用的一種方法,通過這個方法獲得恰當?shù)腃lass對象的引用,就可以在運行時使用類型信息了。
如果你已經(jīng)有了一個感興趣的類型的對象,則可以通過跟類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()方法創(chuàng)建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里面使用到的方法,你可以在運行時發(fā)現(xiàn)一個對象完整的類繼承結構。
通過使用Class的newInstance()方法是實現(xiàn)“虛擬構造器”的一種途徑,用來創(chuàng)建Class的實例,得到的是Object引用,但是引用時指向Letter對象。使用newInstance()來創(chuàng)建的類,必須帶有默認的構造器。(而通過反射API,可以用任意的構造器來動態(tài)的創(chuàng)建類的對象)。
2.3、類字面常量:
除了使用getName()方法,Java還提供了另一種方法來生成對Class對象的引用,即使用類字面常量:
NewLetter.class;
此方法簡單安全,編譯時就受到檢查,更高效。不僅可用于普通類,也可以用于接口,數(shù)組以及基本數(shù)據(jù)類型。另外,對于基本數(shù)據(jù)類型的包裝器類,還有一個標準字段TYPE,TYPE字段是一個引用,執(zhí)行對應的基本數(shù)據(jù)類型的Class對象。為了統(tǒng)一,建議都使用.class這種形式。
2.4、使用.class與使用getName()方法創(chuàng)建對象引用的區(qū)別:
使用.class創(chuàng)建時,不會自動的初始化Class對象。創(chuàng)建步驟如下:
(1)加載 由類加載器執(zhí)行:查找字節(jié)碼(通常是在classpath指定的路徑中查找,但并非必須的),然后從這些字節(jié)碼中創(chuàng)建一個Class對象。
(2)鏈接 將驗證類中的字節(jié)碼,為靜態(tài)域分配存儲空間,如果需要,將會解析這個類創(chuàng)建的對其他類的所有的引用。
(3)初始化 如果該類具有超類,則對其初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊。
初始化被延遲到了對靜態(tài)方法(構造器隱式的是靜態(tài)的)或者非常數(shù)靜態(tài)域進行首次引用時才執(zhí)行的:
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
初始化有效的實現(xiàn)了盡可能的“惰性”。
2.5、下面是判斷是否執(zhí)行初始化的一些情況:
(1)class語法獲得對類的引用不會引發(fā)初始化;
(2)Class.forName()產(chǎn)生了Class引用,立即進行了初始化;
(3)如果一個static final值是“編譯器常量”,那么這個值不需要對類進行初始化就可以被讀取;
(4)如果只是把一個域設置為static final還不足以確保這種行為,例如上面的:
static final double b = Math.random();
(5)如果一個static域bushifinal的,那么在對它訪問時,總是要先進性鏈接和初始化;
2.6、泛化的Class引用:
Class引用表示的是它所指向的對象的確切類型,而該對象便是Class類的一個對象。在JavaSE5中,可以通過泛型對Class引用所指向的Class對象進行限定,并且可以讓編譯器強制執(zhí)行額外的類型檢查:
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<?>優(yōu)于平凡的Class,更建議使用Class<?>,即便它們是等價的,因為Class<?>的好處是它表示你并非是碰巧或者疏忽,而是使用了一個非具體的類引用。
為了限定Class的引用為某種類型,或者該類型的子類型可以將通配符與extends一起使用,創(chuàng)建一個范圍:
Class<? extends Number> num = int.class;// num的引用范圍為Number及其子類,所以可以按照如下賦值num = double.class;num = Number.class;
2.6.2、泛型下的newInstance()方法:
使用了泛型后的Class,調(diào)用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();// 通過獲取的超類引用,只能創(chuàng)建返回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;
可以發(fā)現(xiàn),使用cast()方法的做了額外的工作,這種轉換方法可以用在一下的情況中:在編寫泛型帶的時候,如果存儲了Class引用,并希望通過這個Class引用來執(zhí)行轉型,就可以使用cast()方法。
3、類型檢查 instanceof
3.1、類型轉換前先做檢查
編譯器允許你自由的做向上轉型的賦值操作,而不需要任何顯示的轉型操作,就好像給超類的引用賦值那樣。
然而如果不使用顯示的類型轉換,編譯器就不允許你執(zhí)行向下轉換賦值,這個時候我們不妨先來檢查一下對象是不是某個特定類型的實例,使用到了關鍵字 instanceof:
if(x instanceof Dog) ((Dog) x).bark();
3.2、RTTI的形式:
所以,到目前為止,我們知道RTTI的形式包括:
(1)傳統(tǒng)的類型轉換 (Shape)
(2)代表對象的類型的Class對象
(3)關鍵字instanceof
3.3、動態(tài)的instanceof方法:
Class.isInstance方法提供給了一種動態(tài)測試對象的途徑。
下面演示下instanceof和Class.isInstance的用法:
Attribute:
public interface Attribute {}Shape:
/** * 創(chuàng)建一個抽象類 */public abstract class Shape{ // this調(diào)用了當前類的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));可以發(fā)現(xiàn),instanceof或者Class.isInstance方法判斷了是否繼承體系的實例,即除了判斷本身,還判斷是否超類或接口的實例。
下面演示下使用動態(tài)的Class.instance的用法:
首先創(chuàng)建一個抽象的形狀生成器類:
public abstract class ShapeCreator { private Random rand = new Random(10); // 返回一個對象類型數(shù)組,由實現(xiàn)類提供,后面會看到兩種實現(xiàn)形式,基于forName的和基于類字面常量的.class public abstract List<Class<? extends Shape>> types(); // 隨機生成一個對象類型數(shù)組中的類型對象實例 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; } } // 生成一個隨機數(shù)組 public Shape[] createArray(int size){ Shape[] result = new Shape[size]; for(int i=0; i<size; i++){ result[i] = randomShape(); } return result; } // 生成一個隨機數(shù)組,泛型的ArrayList public ArrayList<Shape> arrayList(int size){ ArrayList<Shape> result = new ArrayList<Shape>(); Collections.addAll(result, createArray(size)); return result; }}接下來編寫一個該抽象類的實現(xiàn):
/** * forName的生成器實現(xiàn) * @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(); } } } // 初始化加載所需的類型數(shù)組 static { loader(); } public List<Class<? extends Shape>> types() { return types; }}最后寫一個統(tǒng)計形狀個數(shù)的類,里面用到了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關鍵字統(tǒng)計對象類型 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()); }}改寫一下抽象類的實現(xiàn),重新用類字面常量實現(xiàn):
/** * 字面量的生成器實現(xiàn) */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); }}現(xiàn)在使用Class.instance統(tǒng)計形狀的個數(shù)如下:
/** * 通過使用Class.instanceof動態(tài)的測試對象,移除掉原來的ShapeCount中單調(diào)的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()統(tǒng)計對象類型 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()); }}現(xiàn)在生成器有了兩種實現(xiàn),我們在這里可以添加一層外觀,設置默認的實現(xiàn)方式:
/** * 現(xiàn)在生成器有了兩種實現(xiàn),我們在這里添加一層外觀,設置默認的實現(xiàn)方式 */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
新聞熱點
疑難解答