Java编程思想: 类型信息

狂风中的少年 提交于 2019-12-25 09:30:15

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

为什么需要RTTI

面向对象编程的主要目的是: 让代码只操纵对基类的引用, 这样方便扩展. 在运行期间, 通过RTTI来确定具体的对象, 并通过多态来确定具体的方法调用.

Class对象

Class对象: 包含一个类有关的信息的特殊对象.

对于类加载器来说, 它首先加载原生类, 如Java API. 其次, 如果一个类的静态成员被引用, 则进行动态加载(这从侧面说明构造函数实际上为static).

class A {
  static {
    System.out.println("Loading A.");
  }
}
class B {
  static {
    System.out.println("Loading B.");
  }
}
public class Test {
  public static void main(String[] args) {
    new A();
    try {
      Class.forName("B");
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }
}

当我们执行: javac Test.java时候, 会生成A.class, B.class, Test.class

但这里必须明确一点的是: 无论A, 还是B的class对象, 均未被载入内存. 只有当我们运行: java Test时候, 会实际调用A/B对象时候, 才会将其class对象载入内存中.

验证的方式也很简单: 如果一个class对象没有被加载, 那么类加载器会根据其类名寻找其.class对象进行加载. 我们将B.class文件删除, 则运行java Test时候直接报异常, 说class B并未寻找到.

 

关于Class对象, 我们可以使用Class.forName来创建, 也可以通过具体的对象: object.getClass()来获取. 以下例子是对Class对象的简单说明:

interface A{}
interface B{}
class C {
  C() {}
}
class D extends C implements A, B {
  D() {}
}
public class Test {
  public static void printInfo(Class c) {
    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 {
      c = Class.forName("D");
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    printInfo(c);

    for (Class face: c.getInterfaces()) {
      printInfo(face);
    }

    Class up = c.getSuperclass();
    printInfo(up);
    
    Object obj = null;
    try {
      obj = up.newInstance();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }
    printInfo(obj.getClass());
  }
}

对代码进行的方法进行解释:

c.getName(): 获取其类名.

c.isInterface(): 判断是否为接口.

c.getSimpleName(): 获取其不包含包名的类名

c.getCanonicalName(): 获取其全限定的类名(含包名)

c.getInterfaces(): 获取其所包含的接口.

c.getSuperClass(): 获取其父类的class对象.

up.newInstance(): 通过class对象调用默认构造函数新建一个对象.

 

类字面常量

使用FancyToy.class来生成class对象. 类字面常量不仅可以应用于普通类, 也可以应用于接口, 数组以及基本数据类型.

当使用".class"来创建对Class对象的引用时, 不会自动的初始化Class对象. 为了使用类而做了三个准备:

1. 加载: 由类加载器执行. 该步骤将查找字节码, 并从这些字节码中创建一个Class对象.

2. 链接: 在链接阶段将验证类中的字节码, 为静态域分配存储空间, 并且如果必须的话, 将解析这个类创建的对其他类的所有引用.

3. 初始化. 如果该类具有超类, 则对其初始化, 执行静态初始化器和静态初始化块.

import java.util.*;

class Initable {
  static final int staticFinal = 47;
  static final int staticFinal2 = Test.rand.nextInt(1000);
  static {
    System.out.println("Initializing Initable");
  }
}

class Initable2 {
  static int staticNonFinal = 147;
  static {
    System.out.println("Initializing Initable2");
  }
}

class Initable3 {
  static int staticNonFinal = 74;
  static {
    System.out.println("Initializing Initable3");
  }
}
public class Test {
  public static Random rand = new Random(47);
  public static void main(String[] args) throws Exception {
    Class initable = Initable.class;
    System.out.println("After creating Initable ref");
    System.out.println(Initable.staticFinal);
    System.out.println(Initable.staticFinal2);

    System.out.println(Initable2.staticNonFinal);

    Class initable3 = Class.forName("Initable3");
    System.out.println("After creating Initable3 ref");
    System.out.println(Initable3.staticNonFinal);
  }
}

输出:

After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74

 

类型转换时的检查

一般有三种RTTI格式:

1. 传统的强制类型转换.

2. 代表对象的类型的Class对象. 通过查询Class对象可以获取运行时所需的信息.

3. 使用instanceof, 它返回一个布尔值, 告诉我们对象是不是某个特定类型的实例.

针对Class对象, 有一个知识点是: class A extends B, 但A.class和B.clas并无任何的关联, 即不能将B.class的对象强制转换未A.class对象:

import java.util.*;

class A {}
class B extends A{}
class C extends B {}

public class Test {
  public static void main(String[] args) throws Exception {
    List<Class<? extends A>> list = new ArrayList<>();
    list.add(A.class);
    list.add(B.class);
    list.add(C.class);

    Class c1 = B.class;
    // ERROR
    // Class c2 = (A.class)c1;

    System.out.println(list.get(1).newInstance() instanceof A);
    System.out.println(list.get(1) instanceof Class);
  }
}

这里有三个知识点:

1) <? extends A> 是泛型的一个语法, 代表所有继承于A的类型.

2) 类B继承于类A, 但B.class跟A.class并无任何的关联.

3) instanceof是对象实例之间的判断, 而Class本身也是对象, 所以可以使用instanceof来判断.

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!