运行时识别对象和类的信息,主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。

RTTI

RTTI的含义:在运行时,识别一个对象的类型
RTTI最基本的使用形式:所有的类型转换都是在运行时进行正确性检查的。

Class对象

Class对象结束用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。
类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的JVM将使用“类加载器”。
类加载器可以包含一条类加载器链,但只有一个原生类加载器。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常是从本地盘加载的。
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时(构造器也是类的静态方法,使用new操作符创建类的新对象也会被当作对类的静态成员的引用),就会加载这个类。

动态加载:Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。

类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一)。
一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。下面的示范程序可以证明这一点。

  1. class Candy{
  2. static { System.out.println("Loading Candy");}
  3. }
  4. class Gum{
  5. static { System.out.println("Loading Gum");}
  6. }
  7. class Cookie{
  8. static { System.out.println("Loading Cookie");}
  9. }
  10. public class SweetShop {
  11. public static void main(String[] args){
  12. System.out.println("inside main");
  13. new Candy();
  14. System.out.println("After creating Candy");
  15. try{
  16. Class.forName("Gum");
  17. }catch (ClassNotFoundException e){
  18. System.out.println("Couldn't find Gum");
  19. }
  20. System.out.println("After Class.forName(\"Gum\")");
  21. new Cookie();
  22. System.out.println("After creating Cookie");
  23. }
  24. }
  25. /* Output:
  26. inside main
  27. Loading Candy
  28. After creating Candy
  29. Loading Gum
  30. After Class.forName("Gum")
  31. Loading Cookie
  32. After creating Cookie
  33. */
  34. /*
  35. Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
  36. */

forName()方法是Class类(所有Class对象都属于这个类)的一个static成员。forName()是取得Class对象的引用的一种方法。它是用一个包含目标类的文本名(注意拼写和大小写)的String作输入参数,返回的是一个Class对象的引用。对forName()的调用的“副作用”:如果类还没被加载就加载它。

想要在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径。也可以通过调用getClass()方法来获取Class引用,这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用。Class包含很多有用的方法,下面是其中的一部分:

  1. package typeinfo;
  2. interface HasBatteries{}
  3. interface Waterproof{}
  4. interface Shoots{}
  5. class Toy{
  6. Toy(){}
  7. Toy(int i){}
  8. }
  9. class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
  10. FancyToy(){
  11. super(1);
  12. }
  13. }
  14. public class ToyTest {
  15. static void printInfo(Class cc){
  16. System.out.println("Class name: "+cc.getName()+" is interface? ["+cc.isInterface()+"]");
  17. System.out.println("Simple name : "+cc.getSimpleName());
  18. System.out.println("Canonical name : "+cc.getCanonicalName());
  19. }
  20. public static void main(String[] args){
  21. Class c=null;
  22. try{
  23. c=Class.forName("typeinfo.FancyToy");
  24. }catch (ClassNotFoundException e){
  25. System.out.println("Couldn't find FancyToy");
  26. System.exit(1);
  27. }
  28. printInfo(c);
  29. for(Class face : c.getInterfaces()){
  30. printInfo(face);
  31. }
  32. Class up =c.getSuperclass();
  33. Object obj=null;
  34. try{
  35. obj=up.newInstance();
  36. }catch (InstantiationException e){
  37. System.out.println("Cannot instantiate");
  38. System.exit(1);
  39. }catch (IllegalAccessException e){
  40. System.out.println("Cannot access");
  41. System.exit(1);
  42. }
  43. printInfo(obj.getClass());
  44. }
  45. }
  46. /* Output:
  47. Class name: typeinfo.FancyToy is interface? [false]
  48. Simple name : FancyToy
  49. Canonical name : typeinfo.FancyToy
  50. Class name: typeinfo.HasBatteries is interface? [true]
  51. Simple name : HasBatteries
  52. Canonical name : typeinfo.HasBatteries
  53. Class name: typeinfo.Waterproof is interface? [true]
  54. Simple name : Waterproof
  55. Canonical name : typeinfo.Waterproof
  56. Class name: typeinfo.Shoots is interface? [true]
  57. Simple name : Shoots
  58. Canonical name : typeinfo.Shoots
  59. Class name: typeinfo.Toy is interface? [false]
  60. Simple name : Toy
  61. Canonical name : typeinfo.Toy
  62. */

类字面常量

使用类字面常量来生成对Class对象的引用。
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示:

···等价于···
boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

注意:当使用“.class”来创建对Class对象的引用时不会自动地初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤:

  • 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。
  • 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
  • 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行:

  1. import java.util.*;
  2. class Initable{
  3. static final int staticFinal=47;
  4. static final int staticFinal2=ClassInitialization.rand.nextInt(1000);
  5. static {
  6. System.out.println("Initializing Initable");
  7. }
  8. }
  9. class Initable2{
  10. static int staticNonFinal=147;
  11. static {
  12. System.out.println("Initializing Initable2");
  13. }
  14. }
  15. class Initable3{
  16. static int staticNonFinal=74;
  17. static {
  18. System.out.println("Initializing Initable3");
  19. }
  20. }
  21. public class ClassInitialization {
  22. public static Random rand =new Random(47);
  23. public static void main(String[] args) throws Exception{
  24. Class initable = Initable.class;
  25. System.out.println("After creating Initable ref");
  26. System.out.println(Initable.staticFinal);
  27. System.out.println(Initable.staticFinal2);
  28. System.out.println(Initable2.staticNonFinal);
  29. Class initable3=Class.forName("Initable3");
  30. System.out.println("After creating Initable3 ref");
  31. System.out.println(Initable3.staticNonFinal);
  32. }
  33. }
  34. /* Output:
  35. After creating Initable ref
  36. 47
  37. Initializing Initable
  38. 258
  39. Initializing Initable2
  40. 147
  41. Initializing Initable3
  42. After creating Initable3 ref
  43. 74
  44. */

RTTI形式包括:

  • 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出了一个ClassCastException异常。
  • 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
  • 关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。