zoukankan      html  css  js  c++  java
  • 类型信息(RTTI和反射)——RTTI

    运行时类型信息可以让你在程序运行时发现和使用类型信息。

    在Java中运行时识别对象和类的信息有两种方式:传统的RTTI,以及反射。下面就先来说下RTTI。

    1、RTTI:

    RTTI:在运行时,识别一个对象的类型。但是这个类型在编译时必须已知

    下面通过一个例子来看下RTTI的使用。这里涉及到了多态的概念:让代码只操作基类的引用(面向对象编程中基本的目的),而实际上调用具体的子类的方法,通常会创建一个具体的对象(Circle,Square,或者Triangle,见下例),把它向上转型为Shape(忽略了对象的具体类型),并在后面的程序中使用匿名(即不知道具体类型)的Shape引用:

    20165985229239.png (515×293)

    abstract class Shape {
      // this 调用当前类的toString()方法,返回实际的内容
      void draw(){ System.out.println(this + ".draw()"); }
      // 声明 toString()为abstract类型,强制集成在重写该方法
      abstract public String toString();
    }
     
    class Circle extends Shape {
      public String toString(){ return "Circle"; }
    }
     
    class Square extends Shape {
      public String toString(){ return "Square"; }
    }
     
    class Triangle extends Shape {
      public String toString(){ return "Triangle"; }
    }
     
    public static void main(String[] args){
      // 把Shape对象放入List<Shape>的数组的时候会向上转型为Shape,从而丢失了具体的类型信息
      List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());
      // 从数组中取出时,这种容器,实际上所有的元素都当成Object持有,会自动将结果转型为Shape,这就是RTTI的基本的使用。
      for(Shape shape : shapeList){
        shape.draw();
      }
    }

    输出结果为:

    Circle.draw()
    Square.draw()
    Triangle.draw()

    存入数组的时候,会自动向上转型为Shape,丢失了具体的类型,当从数组中取出的时候,(List容器将所有的事物都当做Object持有),会自动将结果转型回Shape,这就是RTTI的基本用法

    Java中所有的类型转换都是在运行时进行正确性检查的,也就是RTTI:在运行时,识别一个对象的类型。

    上面的转型并不彻底,数组的元素取出时由Object转型为Shape,而不是具体的类型。这是因为目前我们只知道这个List<Shape>保存的都是Shape。编译时这是由容器和Java泛型系统来确保这一点的,而在运行时由类型转换操作来确保这一点的。

    而能够通过Shape对象执行到子类的具体代码就是由多态来决定的了,具体看Shape引用所指向的具体对象。

    另外,使用RTTI,可以查询某个Shape引用所指向的对象的确切类型,然后选择性的执行子类的方法。

    2、Class对象:
    要了解RTTI在Java中的工作原理,必须知道类型信息在运行时是如何表示的,这里是由Class这个特殊对象完成的。

    Class对象是用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI。

    每当编译一个新类,就会产生一个Class对象(.class文件)。运行这个程序的JVM将使用“类加载器”这个子系统。

    类加载器子系统:包含一条类加载器链,但只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载可信类,包括Java API类,通常是从本地磁盘加载的。当需要以某种特定的方式加载类,以支持Web服务器应用,可以挂接额外的类加载器。

    2.1、加载类的时机:
    当程序创建第一个对类的静态成员的引用时,就会加载这个类。这证明其实构造器也是类的静态方法,当使用new操作符创建类的新对象也会当做对类的静态成员的引用。

    可见Java程序时动态加载的,按需加载。需要用到Class时,类加载器首先会检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找到.class文件。接下来是验证阶段:加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。

    2.2、Class相关方法,newInstance()
    下面通过一个例子演示Class对象的加载:

    class A {
      // 静态代码库,在第一次被加载时执行,通过打印信息知道该类什么时候被加载
      static { System.out.println("Loading A"); }
    }
    class B {
      static { System.out.println("Loading B"); }
    }
    class C {
      static { System.out.println("Loading C"); }
    }
    public class Load {
      public static void main(String[] args){
        System.out.println("execute main...");
        new A();
        System.out.println("after new A");
        try {
          Class.forName("com.itzhai.test.type.B");
        } catch (ClassNotFoundException e) {
          System.out.println("cloud not find class B");
        }
        System.out.println("after Class.forName B");
        new C();
        System.out.println("after new C");
      }
    }

    输出结果为:

    execute main...
    Loading A
    after new A
    Loading B
    after Class.forName B
    Loading C
    after new C

    可见,Class对象在需要的时候才被加载,注意到这里的Class.forName()方法:

    forName()方法是取得Class对象的引用的一种方法,通过这个方法获得恰当的Class对象的引用,就可以在运行时使用类型信息了。

    如果你已经有了一个感兴趣的类型的对象,则可以通过跟类Object提供的getClass()方法来获得Class引用。

    下面是一段Class的使用的代码:

    interface X{}
    interface Y{}
    interface Z{}
    class Letter {
      Letter(){};
      Letter(int i){};
    }
    class NewLetter extends Letter implements X, Y, Z{
      NewLetter(){ super(1); };
    }
    public class ClassTest {
     
      /**
       * 打印类型信息
       * @param c
       */
      static void printInfo(Class c){
        // getName()获得全限定的类名
        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 {
          // 获得Class引用
          c = Class.forName("com.itzhai.test.type.NewLetter");
        } catch (ClassNotFoundException e) {
          System.out.println("Can not find com.itzhai.test.type.NewLetter");
          System.exit(1);
        }
        // 打印接口类型信息
        for(Class face : c.getInterfaces()){
          printInfo(face);
        }
        // 获取超类Class引用
        Class up = c.getSuperclass();
        Object obj = null;
        try {
          // 通过newInstance()方法创建Class的实例
          obj = up.newInstance();
        } catch (InstantiationException e) {
          System.out.println("Can not instantiate");
        } catch (IllegalAccessException e) {
          System.out.println("Can not access");
        }
        // 打印超类类型信息
        printInfo(obj.getClass());
      }
    }

    输出为:

    Class name: com.itzhai.test.type.X is interface? true
    Simple name: X
    Canonical name: com.itzhai.test.type.X
    Class name: com.itzhai.test.type.Y is interface? true
    Simple name: Y
    Canonical name: com.itzhai.test.type.Y
    Class name: com.itzhai.test.type.Z is interface? true
    Simple name: Z
    Canonical name: com.itzhai.test.type.Z
    Class name: com.itzhai.test.type.Letter is interface? false
    Simple name: Letter
    Canonical name: com.itzhai.test.type.Letter

    注意,在传递给forName()的字符串必须使用全限定名(包括包名)

    通过printInfo里面使用到的方法,你可以在运行时发现一个对象完整的类继承结构。

    通过使用Class的newInstance()方法是实现“虚拟构造器”的一种途径,用来创建Class的实例,得到的是Object引用,但是引用时指向Letter对象。使用newInstance()来创建的类,必须带有默认的构造器。(而通过反射API,可以用任意的构造器来动态的创建类的对象)。

    2.3、类字面常量:
    除了使用forName()方法,Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量:

    NewLetter.class;

    此方法简单安全,编译时就受到检查,更高效。不仅可用于普通类,也可以用于接口数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE,TYPE字段是一个引用,执行对应的基本数据类型的Class对象。为了统一,建议都使用.class这种形式。

    2.4、使用.class与使用forName()方法创建对象引用的区别:
    使用.class创建时,不会自动的初始化Class对象。创建步骤如下:

    (1)加载 由类加载器执行:查找字节码(通常是在classpath指定的路径中查找,但并非必须的),然后从这些字节码中创建一个Class对象。

    (2)链接 将验证类中的字节码,为静态域分配存储空间,如果需要,将会解析这个类创建的对其他类的所有的引用。

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

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

    class Data1{
      static final int a = 1;
      static final double b = Math.random();
      static {
        System.out.println("init Data1...");
      }
    }
     
    class Data2{
      static int a = 12;
      static {
        System.out.println("init Data2...");
      }
    }
     
    class Data3{
      static int a = 23;
      static {
        System.out.println("init Data3...");
      }
    }
     
    public class ClassTest2 {
      public static void main(String[] args){
        System.out.println("Data1.class: ");
        Class data1 = Data1.class;
        System.out.println(Data1.a); // 没有初始化Data1
        System.out.println(Data1.b); // 初始化了Data1
        System.out.println(Data2.a); // 初始化了Data2
        try {
          Class data3 = Class.forName("com.itzhai.test.type.Data3"); // 初始化了Data3
        } catch (ClassNotFoundException e) {
          System.out.println("can not found com.itzhai.test.type.Data3...");
        }
        System.out.println(Data3.a);
      }
    }

    输出的结果为:

    Data1.class:
    1
    init Data1...
    0.26771085109184534
    init Data2...
    12
    init Data3...
    23

    初始化有效的实现了尽可能的“惰性”。

    2.5、下面是判断是否执行初始化的一些情况:
    (1).class语法获得对类的引用不会引发初始化;

    (2)Class.forName()产生了Class引用,立即进行了初始化;

    (3)如果一个static final值是“编译器常量”,那么这个值不需要对类进行初始化就可以被读取;

    (4)如果只是把一个域设置为static final还不足以确保这种行为,例如上面的:

    static final double b = Math.random();

    (5)如果一个static域但不是final的,那么在对它访问时,总是要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间);

    2.6、泛化的Class引用:
    Class引用表示的是它所指向的对象的确切类型,而该对象便是Class类的一个对象。在JavaSE5中,可以通过泛型对Class引用所指向的Class对象进行限定,并且可以让编译器强制执行额外的类型检查:

    Class intCls = int.class;
    // 使用泛型限定Class指向的引用
    Class<Integer> genIntCls = int.class;
    // 没有使用泛型的Clas可以重新赋值为指向任何其他的Class对象
    intCls = double.class;
    // 下面的编译会出错
    // genIntCls = double.class;

    2.6.1、使用通配符?放松泛型的限定:

    Class<?> intCls = int.class;
    intCls = String.class;

    在JavaSE5中,Class<?>优于平凡的Class,更建议使用Class<?>,即便它们是等价的,因为Class<?>的好处是它表示你并非是碰巧或者疏忽,而是使用了一个非具体的类引用。

    为了限定Class的引用为某种类型,或者该类型的子类型可以将通配符与extends一起使用,创建一个范围:

    Class<? extends Number> num = int.class;
    // num的引用范围为Number及其子类,所以可以按照如下赋值
    num = double.class;
    num = Number.class;

    另外,可以使用Class<? Super ziclass>。

     

    2.6.2、泛型下的newInstance()方法:
    使用了泛型后的Class,调用newInstance()返回的对象是确切类型的,但是当你使用getSuperclass()获取泛型对应的超类的时候真正的类型会有一些限制:编译器在编译期就知道了超类的类型,但是,通过这个获取到的超类引用的newInstance()方法返回的不是精确类型,而是Object

    Dog dog = dogCls.newInstance();
    abstract class Animal {
    }
    class Dog extends Animal{
    }
     
    // 下面的写法是错误的,只能返回 Class<? super Dog>类型
    // Class<Animal> animalCls = dogCls.getSuperclass();
    Class<? super Dog> animalCls = dogCls.getSuperclass();
    // 通过获取的超类引用,只能创建返回Object类型的对象
    Object obj = animalCls.newInstance();

    2.6.3、新的转型语法:cast()方法

    直接看下代码:

    Animal animal = new Dog();
    Class<Dog> dogCls = Dog.class;
    Dog dog = dogCls.cast(animal);
    // 或者直接使用下面的转型方法
    dog = (Dog)animal;

    可以发现,使用cast()方法的做了额外的工作,这种转换方法可以用在以下的情况中:在编写泛型代码的时候,如果存储了Class引用,并希望以后通过这个Class引用来执行转型,就可以使用cast()方法。

    3、类型检查
    3.1、类型转换前先做检查
    编译器允许你自由的做向上转型的赋值操作,而不需要任何显示的转型操作,就好像给超类的引用赋值那样。

    然而如果不使用显示的类型转换,编译器就不允许你执行向下转换赋值,这个时候我们不妨先来检查一下对象是不是某个特定类型的实例,使用到了关键字 instanceof:

    if(x instanceof Dog)
      ((Dog) x).bark();

    3.2、RTTI的形式:

    所以,到目前为止,我们知道RTTI的形式包括:

    (1)传统的类型转换 (Shape)

    (2)代表对象的类型的Class对象

    (3)关键字instanceof

    3.3、动态的instanceof方法:
    Class.isInstance方法提供给了一种动态测试对象的途径。

    下面演示下 instanceofClass.isInstance 的用法:

    Attribute:

    public interface Attribute {
     
    }

    Shape:

    /**
     * 创建一个抽象类
     */
    public abstract class Shape{
      // this调用了当前类的toString方法获得信息
      public void draw() { System.out.println(this + ".draw()"); }
      // 声明toString()方法为abstract,从而强制继承者需要重写该方法。
      abstract public String toString();
    }

    Circle:

    public class Circle extends Shape implements Attribute{
      public String toString(){ return "Circle"; }
    }

    Square:

    public class Square extends Shape{
      public String toString(){ return "Square"; }
    }

    Triangle:

    public class Triangle extends Shape{
      public String toString(){ return "Triangle"; }
    }

    类型检查:

    // instanceOf
    Circle c = new Circle();
    // 判断是否超类的实例
    System.out.format("Using instanceof: %s is a shape? %b
    ",
        c.toString(), c instanceof Shape);
    // 判断是否Circle的实例
    System.out.format("Using instanceof: %s is a circle? %b
    ",
        c.toString(), c instanceof Circle);
    // 判断是否超类的实例
    System.out.format("Using Class.isInstance: %s is a shape? %b
    ",
        c.toString(), Shape.class.isInstance(c));
    // 判断是否接口的实例
    System.out.format("Using Class.isInstance: %s is a Attribute? %b
    ", 
        c.toString(), Attribute.class.isInstance(c));

    可以发现,instanceof 或者 Class.isInstance 方法判断了是否继承体系的实例,即除了判断本身,还判断是否超类或接口的实例。

    下面演示下使用动态的Class.isInstance的用法:

    首先创建一个抽象的形状生成器类:

    public abstract class ShapeCreator {
      private Random rand = new Random(10);
      // 返回一个对象类型数组,由实现类提供,后面会看到两种实现形式,基于forName的和基于类字面常量的.class
      public abstract List<Class<? extends Shape>> types();
      // 随机生成一个对象类型数组中的类型对象实例
      public Shape randomShape(){
        int n = rand.nextInt(types().size());
        try {
          return types().get(n).newInstance();
        } catch (InstantiationException e) {
          e.printStackTrace();
          return null;
        } catch (IllegalAccessException e) {
          e.printStackTrace();
          return null;
        }
      }
      // 生成一个随机数组
      public Shape[] createArray(int size){
        Shape[] result = new Shape[size];
        for(int i=0; i<size; i++){
          result[i] = randomShape();
        }
        return result;
      }
      // 生成一个随机数组,泛型的ArrayList
      public ArrayList<Shape> arrayList(int size){
        ArrayList<Shape> result = new ArrayList<Shape>();
        Collections.addAll(result, createArray(size));
        return result;
      }
    }

    接下来编写一个该抽象类的实现:

    /**
     * forName的生成器实现
     * @author arthinking
     *
     */
    public class ForNameCreator extends ShapeCreator{
     
      private static List<Class<? extends Shape>> types =
          new ArrayList<Class<? extends Shape>>();
      private static String[] typeNames = {
        "com.itzhai.javanote.entity.Circle",
        "com.itzhai.javanote.entity.Square",
        "com.itzhai.javanote.entity.Triangle"
      };
     
      @SuppressWarnings("unused")
      private static void loader(){
        for(String name : typeNames){
          try {
            types.add((Class<? extends Shape>)Class.forName(name));
          } catch (ClassNotFoundException e) {
            e.printStackTrace();
          }
        }
      }
      // 初始化加载所需的类型数组
      static {
        loader();
      }
      public List<Class<? extends Shape>> types() {
        return types;
      }
    }

    最后写一个统计形状个数的类,里面用到了instanceof:

    public class ShapeCount {
     
      static class ShapeCounter extends HashMap<String, Integer>{
        public void count(String type){
          Integer quantity = get(type);
          if(quantity == null){
            put(type, 1);
          } else {
            put(type, quantity + 1);
          }
        }
      }
     
      // 演示通过instanceof关键字统计对象类型
      public static void countShapes(ShapeCreator creator){
        ShapeCounter counter = new ShapeCounter();
        for(Shape shape : creator.createArray(20)){
          if(shape instanceof Circle)
            counter.count("Circle");
          if(shape instanceof Square)
            counter.count("Square");
          if(shape instanceof Triangle){
            counter.count("Triangle");
          }
        }
        System.out.println(counter);
      }
     
      public static void main(String[] args){
        countShapes(new ForNameCreator());
      }
    }

    改写一下抽象类的实现,重新用类字面常量实现:

    /**
     * 字面量的生成器实现
     */
    public class LiteralCreator extends ShapeCreator{
     
      public static final List<Class<? extends Shape>> allType =
          Collections.unmodifiableList(Arrays.asList(Circle.class, Triangle.class, Square.class));
     
      public List<Class<? extends Shape>> types(){
        return allType;
      }
     
      public static void main(String[] args){
        System.out.println(allType);
      }
     
    }

    现在使用Class.isInstance统计形状的个数如下:

    /**
     * 通过使用Class.instanceof动态的测试对象,移除掉原来的ShapeCount中单调的instanceof语句
     *
     */
    public class ShapeCount2 {
     
      private static final List<Class<? extends Shape>> shapeTypes = LiteralCreator.allType;
     
      static class ShapeCounter extends HashMap<String, Integer>{
        public void count(String type){
          Integer quantity = get(type);
          if(quantity == null){
            put(type, 1);
          } else {
            put(type, quantity + 1);
          }
        }
      }
     
      // 演示通过Class.isInstance()统计对象类型
      public static void countShapes(ShapeCreator creator){
        ShapeCounter counter = new ShapeCounter();
        for(Shape shape : creator.createArray(20)){
          for(Class<? extends Shape> cls : shapeTypes){
            if(cls.isInstance(shape)){
              counter.count(cls.getSimpleName());
            }
          }
        }
        System.out.println(counter);
      }
     
      public static void main(String[] args){
        countShapes(new ForNameCreator());
      }
    }

    现在生成器有了两种实现,我们在这里可以添加一层外观,设置默认的实现方式:

    /**
     * 现在生成器有了两种实现,我们在这里添加一层外观,设置默认的实现方式
     */
    public class Shapes {
     
      public static final ShapeCreator creator =
          new LiteralCreator();
      public static Shape randomShape(){
        return creator.randomShape();
      }
      public static Shape[] createArray(int size){
        return creator.createArray(size);
      }
      public static ArrayList<Shape> arrayList(int size){
        return creator.arrayList(size);
      }
    }

    3.4、instanceof与Class的等价性:

    instanceof和isInstance()生成的结果完全一样,保持了类型的概念,判断是否一个类或者是这个类的派生类。

    equals()与==也是一样的,而使用这个比较实际的Class对象,就没有考虑继承。

    System.out.println(new Circle() instanceof Circle); // true
    System.out.println(Shape.class.isInstance(new Circle())); // true
    System.out.println((new Circle()).getClass() == Circle.class); // true
    System.out.println((new Circle().getClass()).equals(Shape.class)); // false

    本文摘自《Java编程思想》第14章类型信息,参考博客http://www.jb51.net/article/83784.htm

     
  • 相关阅读:
    oracle热备份与冷备份的对比
    oracle数据库备份
    shell 去除空行
    已有实例 Oracle 下新增实例(2)通过dbca克隆实例
    oracle启动,提示“LRM-00109: could not open parameter file”
    linux——使用fidsk对linux硬盘进行操作【转】
    前端——知识点
    hdu 3996 Gold Mine 最大权闭合子图
    hdu 3917 Road constructions 最大权闭合子图
    poj 2987 Firing 最大权闭合子图
  • 原文地址:https://www.cnblogs.com/atom-wangzh/p/8709492.html
Copyright © 2011-2022 走看看