Java类型信息之RTTI
软件工程的一个核心问题就是软件的复用和扩展。面向对象思想通过封装,继承,派生等机制有效地解决了这个问题。但需求总是变幻莫测,不可琢磨,在面向对象这栋恢宏的大厦旁,还漂浮着一朵乌云,从而导致了RTTI的登场。
正是因为RTTI的存在,在软件世界里,无间道是会分分钟暴露的:
1.缘起
考虑一下面向对象思想发源地之一的CAD系统:
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()
代码点评:
Shape是基类,根据多态机制,Shape引用可以被它的子类引用赋值,并通过动态绑定在虚函数draw调用时正确调用子类的draw。
依靠这种机制,我们可以轻松的在上层的CAD类中写出通用的draw方法,而不用管Shape到底会有多少子类。
现在忽然来了新的需求,要求CAD在draw的时候把用户指定类型的对象高亮显示,假设用户指定了Circle类型,那就是要求把当前图形中的所有的Circle对象高亮显示。因为上层的CAD代码无法分辨具体的子类类型,所以功能无法实现。
痛定思痛,这些面向对象的大师们决定引入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的引用。