运行时类型信息使得你可以在程序运行时发现和使用类型信息。Java是如何让我们在运行时识别对象和类的信息得呢?

主要有两种方式:1.传统RTTI,他假定我们在编译期间已经知道了所有类型;2.反射,它允许我们在运行时发现和使用类的信息。

一、为什么需要RTTI

我们来看一个例子:

 

  这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程中的基本目的是:让代码只操纵对基类(Shape)的引用。这样,如果添加一个新类(比如从Shape派生的Rhomboid)来扩展程序就不会影响原来代码了。这个例子中Shape接口动态绑定了draw()方法,目的就是让客户端程序员用泛化的Shape引用来调用draw()。draw()在所有派生类里都会被覆盖,并且由于它是被动态绑定的,所以即使是通过泛化的Shape引用来调用,也能产生正确的行为。这就是多态。

 

复制代码
abstract class Shape {     void draw() {         System.out.println(this + ".draw()");     }      @Override     abstract public String toString(); }  class Circle extends Shape {     @Override     public String toString() {         return "Circle";     } }  class Square extends Shape {     @Override     public String toString() {         return "Square";     } }  class Triangle extends Shape {     @Override     public String toString() {         return "Triangle";     } }  public class Shapes {     public static void main(String[] args) {         List<Shape> shapes = Arrays.asList(new Circle(), new Triangle(), new Square());         for (Shape shape : shapes) {             shape.draw();         }     } }
复制代码

结果:

复制代码
Circle.draw() Triangle.draw() Square.draw()
复制代码

分析:1.toString()被声明为abstract,以此强制继承者覆写该方法,并且防止对无格式的Shape的实例化。

      2.如果某个对象出现在字符串表达式中(比如“+”,字符串对象的表达式),toString()会自动被调用,以生成该对象的String。

      3.当Shape派生类的对象放入List<Shape>的数组时会向上转型,但是当向上转型为Shape时也丢失了Shape对象的具体类型。对数组而言,他们都只是Shape类的对象。

      4.当从数组中取出元素时(这种容器把所有的事物都当作Object持有),将结果转型会Shape。这是RTTI的最基本使用形式,因为在Java中所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。

      5.但是例子中RTTI转型并不彻底:Object被转型为Shape而不是具体的派生类型,这是因为List<Shape>提供的信息是保存的都是Shape类型。在编译时,由容器和泛型保证这点,在运行时,由类型转换操作来确保这点。

      6.Shape对象具体执行什么,是由引用所指向的具体对象(派生类对象)决定的,而现实中大部分人正是希望尽可能少的了解对象的具体类型,而只是和家族中一个通用的类打交道,如:Shape,使得代码更易读写,设计更好实现,理解和改变,所以“多态”是面向对象编程的基本目标。

二、Class对象

  使用RTTI,可以查询某个Shape引用所指向的对象的确切类型,然后选择或者剔除某些特性。要理解RTTI在Java中的工作原理,首先应该知道类型信息在运行时是如何表示的。Class对象承担了这项工作,Class对象包含了与类有关的信息。Class对象就是用来创建所有对象的,Java使用Class对象来执行其RTTI,Class类除了执行转型操作外,还有拥有大量的使用RTTI的其他方式。

  每当编写并编译一个新类,就会生成一个Class对象,被存在同名的.class文件中。运行这个程序的Java 虚拟机(JVM)使用被称为“类加载器”的子系统,生成这个类的对象。类加载器子系统实际上可以包含以挑类加载链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的通常是加载本地盘的内容,这条链通常不需要添加额外的类加载器,但是如果有特殊需求比如:支持Web服务器应用,那么就要挂接额外的类加载器。

  所有的类在被第一次使用时,动态加载到JVM中。当第一个对类的静态成员的引用被创建时,就会加载这个类。这也证明构造器是静态方法,使用new操作符创建类的新对象也会被当作对类的静态成员的引用。因此,Java程序在开始运行之前并未完全加载,各个部分在必须时才会加载。类加载器首先检查这个类的Class对象是否已加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件。

  一旦某个类的Class对象被加载入内存,它就被用来创建这个类的所有对象:

复制代码
class Candy {