軟件工程的一個核心問題就是軟件的複用和擴展。面向對象思想經過封裝,繼承,派生等機制有效地解決了這個問題。但需求老是變幻莫測,不可琢磨,在面向對象這棟恢宏的大廈旁,還漂浮着一朵烏雲,從而致使了RTTI的登場。javascript
正是由於RTTI的存在,在軟件世界裏,無間道是會分分鐘暴露的:java
1.緣起數組
考慮一下面向對象思想發源地之一的CAD系統:ide
package typeinfo._01_rtti; import java.util.Arrays; import java.util.List; interface Shape { void draw(); } class CAD { public void draw(List<Shape> shapes) { for (Shape shape : shapes) { shape.draw(); } } } class Circle implements Shape { @Override public void draw() { System.out.println("Circle draw()"); } } class Square implements Shape { @Override public void draw() { System.out.println("Square draw()"); } } class Triangle implements Shape { @Override public void draw() { System.out.println("Triangle draw()"); } } public class Shapes { public static void main(String[] args) { List<Shape> shapes = Arrays.asList( new Circle(), new Square(), new Triangle() ); CAD cad = new CAD(); cad.draw(shapes); } }
輸出結果:函數
Circle draw()
Square draw()
Triangle draw()測試
代碼點評:spa
Shape是基類,根據多態機制,Shape引用能夠被它的子類引用賦值,並經過動態綁定在虛函數draw調用時正確調用子類的draw。code
依靠這種機制,咱們能夠輕鬆的在上層的CAD類中寫出通用的draw方法,而不用管Shape到底會有多少子類。對象
如今突然來了新的需求,要求CAD在draw的時候把用戶指定類型的對象高亮顯示,假設用戶指定了Circle類型,那就是要求把當前圖形中的全部的Circle對象高亮顯示。由於上層的CAD代碼沒法分辨具體的子類類型,因此功能沒法實現。blog
痛定思痛,這些面向對象的大師們決定引入RTTI(Run-Time Type Identification)運行時類型識別,簡單的說就是運行時你只要給我一個引用,就我能準確的告訴你它是什麼具體類型的。
儘管RTTI名聲很差,大師們的建議是能少用就少用,可是具備諷刺意味的是,幾乎沒有哪門語言能跳過RTTI,不一樣的只是各類各樣的語法區別:
Python語言的type
Java語言的getClass
javascript語言的typeof
C++語言的typeid
C#語言的GetType
。。。
2.Java語言的class
Java語言的class實際上是一個Class類的對象,任何類都有一個這樣的對象,實際上,這是對象又是Java類加載器加載類時自動帶上的,要理清這些角色之間的複雜關係,請看下圖:
全部new出來的對象共享該類型的class對象
class對象是Class類的一個實例
Class類是被ClassLoader帶進來的
ClassLoader經過加載class字節碼導入類
口說無憑,直接來段測試代碼吧:
1 @Test 2 public void testClassLevel() { 3 class Test { 4 } 5 6 Test a = new Test(); 7 System.out.println(a.getClass()); 8 Test b = new Test(); 9 System.out.println(b.getClass()); 10 11 System.out.println(a.getClass() == a.getClass()); 12 13 System.out.println(Test.class); 14 System.out.println(Test.class.getClassLoader()); 15 }
輸出結果:
class typeinfo._01_rtti.ClassClassTest$1Test
class typeinfo._01_rtti.ClassClassTest$1Test
true
class typeinfo._01_rtti.ClassClassTest$1Test
jdk.internal.loader.ClassLoaders$AppClassLoader@726f3b58
代碼點評:
new出來的對象能夠經過getClass獲得該類型的class對象
全部new出來的對象getClass獲得的class對象都是同一個
class對象能夠經過Test.class的形式快速引用
class對象能夠經過getClassLoader獲得類加載器
關於類加載器的細節,將單獨有一篇文章介紹
2.引用class
引用class最簡單的方式就是經過XXX.class的語法直接引用:
public void print(Class meta) { System.out.println(meta); } @Test public void testClassClass() { print(boolean.class); print(char.class); print(byte.class); print(short.class); print(int.class); print(long.class); print(float.class); print(double.class); print(void.class); print(Object.class); print(String.class); }
輸出結果:
boolean
char
byte
short
int
long
float
double
void
class java.lang.Object
class java.lang.String
代碼點評:
沒錯,基本類型也有class,甚至是void也有class
class及其Class就是Java類型體系的核心
還有內置的數組呢?一樣不能例外:
@Test public void testClassClassArray() { print(boolean[].class); print(char[].class); print(byte[].class); print(short[].class); print(int[].class); print(long[].class); print(float[].class); print(double[].class); //print(void[].class); print(Object[].class); print(String[].class); }
輸出結果:
class [Z
class [C
class [B
class [S
class [I
class [J
class [F
class [D
class [Ljava.lang.Object;
class [Ljava.lang.String;
代碼點評:
void[]是不存在的
輸出的這些奇怪的字符,這是歷史緣由形成的,其實它就表示數組
經過對象獲得class要用getClass:
1 @Test 2 public void testClassGetClass() { 3 Boolean t = true; 4 Character c = 'A'; 5 Byte b = 0; 6 Short s = 0; 7 Integer i = 0; 8 Long l = 0L; 9 Float f = 1.0f; 10 Double d = 1.0; 11 12 print(t.getClass()); 13 print(c.getClass()); 14 print(b.getClass()); 15 print(s.getClass()); 16 print(i.getClass()); 17 print(l.getClass()); 18 print(f.getClass()); 19 print(d.getClass()); 20 21 //Void v = new Void(); 22 Object obj = new Object(); 23 String str = new String(); 24 //print(v.getClass()); 25 print(obj.getClass()); 26 print(str.getClass()); 27 }
輸出結果:
class java.lang.Boolean
class java.lang.Character
class java.lang.Byte
class java.lang.Short
class java.lang.Integer
class java.lang.Long
class java.lang.Float
class java.lang.Double
class java.lang.Object
class java.lang.String
代碼點評:
基本類型不能生成對象引用,只好用它們的包裝類對象引用
除了Void,其它的與XXX.class是一致的
其實數組也是對象,因此下面的代碼也是成立的:
1 @Test 2 public void testClassGetClassArray() { 3 boolean[] t = {true}; 4 char[] c = {'A'}; 5 byte[] b = {0}; 6 short[] s = {0}; 7 int[] i = {0}; 8 long l[] = {0L}; 9 float f[] = {1.0f}; 10 double[] d = {1.0}; 11 12 print(t.getClass()); 13 print(c.getClass()); 14 print(b.getClass()); 15 print(s.getClass()); 16 print(i.getClass()); 17 print(l.getClass()); 18 print(f.getClass()); 19 print(d.getClass()); 20 21 Object[] obj = {new Object()}; 22 String[] str = {new String()}; 23 print(obj.getClass()); 24 print(str.getClass()); 25 }
輸出結果:
class [Z
class [C
class [B
class [S
class [I
class [J
class [F
class [D
class [Ljava.lang.Object;
class [Ljava.lang.String;
對於Java的基本類型,還能夠經過XXX.TYPE的形式引用class,這是基本類型所特有的綠色通道:
1 @Test 2 public void testClassType() { 3 print(Boolean.TYPE); 4 print(Character.TYPE); 5 print(Byte.TYPE); 6 print(Short.TYPE); 7 print(Integer.TYPE); 8 print(Long.TYPE); 9 print(Float.TYPE); 10 print(Double.TYPE); 11 12 print(Void.TYPE); 13 //print(Object.TYPE); 14 //print(String.TYPE); 15 }
輸出結果:
boolean
char
byte
short
int
long
float
double
void
3.class與泛型
原則上,全部的class都是Class類的對象,可是這太粗略了。咱們能夠經過泛型進一步細化Class類:
1 @Test 2 public void testGeneric() { 3 Class intClass = int.class; 4 intClass = double.class; 5 6 Class<Integer> genericIntClass = int.class; 7 genericIntClass = Integer.class; // Same thing 8 // genericIntClass = double.class; // Illegal 9 }
代碼點評:
默認的Class便可以引用int.class,也能夠引用double.class
Class<Integer>就只能引用int.class了,不能夠引用double.class
固然了,在泛型的世界裏,更習慣用通配符Class<?>代替Class,以代表它那高貴的泛型身份:
1 @Test 2 public void testWildcard() { 3 Class<?> intClass = int.class; 4 intClass = double.class; 5 intClass = String.class; 6 }
固然了,還不能忘了泛型的限定符:
1 @Test 2 public void testBounded() { 3 Class<? extends Number> numberClass = int.class; 4 numberClass = double.class; 5 numberClass = Number.class; 6 //numberClass = String.class; 7 }
這裏經過限定符的限制,有效的避免了String.class的引用。