Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1966987
  • 博文数量: 606
  • 博客积分: 9991
  • 博客等级: 中将
  • 技术积分: 5725
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-17 19:07
文章分类

全部博文(606)

文章存档

2011年(10)

2010年(67)

2009年(155)

2008年(386)

分类:

2008-09-12 22:51:58

第十章 类型检查
 

运行时类型识别(run-time type identification,RTTI)的概念初看起来非常简单;当只有一个指向对象基类的引用时,RTTI机制可以让你找出这个对象的确切类型。

运行时识别对象和类的信息。只要有两种方式:1.“传统的”RTTI,它假定我们在编译时和运行时已经知道了所有的类型;2.“反射机制”,它运行我们在运行时获得类的信息。

RTTI

import com.bruceeckel.simpletest.*;

class Shape {
  void draw() { System.out.println(this + ".draw()"); }
}

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 class Shapes {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    // Array of Object, not Shape:

    Object[] shapeList = {
      new Circle(),
      new Square(),
      new Triangle()
    };
    for(int i = 0; i < shapeList.length; i++)
      ((Shape)shapeList[i]).draw(); // Must cast

    monitor.expect(new String[] {
      "Circle.draw()",
      "Square.draw()",
      "Triangle.draw()"
    });
  }
} ///:~

   在这个例子中,RTTI类型转换并不彻底,Object被转型为Shape,而不是转型为Circle、Square或者Triangle。这是因为目前我们只知道这个数组保存的都是Shape。在编译时,这只能由你自己设定的规则来强制确保这一点;而在运行时,有类型转换操作来确保这一点。接下来就是多态机制的事情了,Shape对象实际执行什么样的代码,是由引用所执行的具体对象Circle、Square后者Triangle而决定的。

   Class对象

   在运行时,当我们像生成这个类的对象时,运行这个程序的Java虚拟机(JVM)首先检查这个类的Class对象是否已经加载。如果尚未加载,JVM就会根据类名查找.Class文件,并将其载入。所以Java程序并不是一开始就被完全加载的,这一点域传语言都不同。

import com.bruceeckel.simpletest.*;

class Candy {
  static {
    System.out.println("Loading Candy");
  }
}

class Gum {
  static {
    System.out.println("Loading Gum");
  }
}

class Cookie {
  static {
    System.out.println("Loading Cookie");
  }
}

public class SweetShop {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    System.out.println("inside main");
    new Candy();
    System.out.println("After creating Candy");
    try {
      Class.forName("Gum");
    } catch(ClassNotFoundException e) {
      System.out.println("Couldn't find Gum");
    }
    System.out.println("After Class.forName(\"Gum\")");
    new Cookie();
    System.out.println("After creating Cookie");
    monitor.expect(new String[] {
      "inside main",
      "Loading Candy",
      "After creating Candy",
      "Loading Gum",
      "After Class.forName(\"Gum\")",
      "Loading Cookie",
      "After creating Cookie"
    });
  }
}

    Class对象仅在需要的时候才被加载。

    Class.forName("Gum");

    forName()是取得Class对象的引用的一种方法。对forName()的调用是为了它产生的“副作用”;如果类Gum还没有被加载就加载它

    类字面常量

    上面的语句等价与Gum.class,这是一种“类字面常量”。这样做不仅简单,而且更安全,因为它在编译时就会收到检查。并且它无需方法调用,所以也更高效。类字面产量不仅可以应用域普通类,也可以用应用于接口、数组及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对象的基本数据类型的Class对象,(例如int.class == Integer.TYPE),但建议使用".class"形式,保持与普通类的一致性。

    类型转换前先做检查

    RTTI形式包括

    1)传统的类型转换,如“(Shape)”,由RTTI确保类型转换的准确性,如果转型了一个错误的类型转换,就会抛出一个ClassCastException异常。

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

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

    if( x instanceof Dog)  ((Dog)x).bark();使用instanceof非常重要,否则会得到一个ClassCastException异常。

package c10;
import com.bruceeckel.simpletest.*;
import java.util.*;

public class PetCount {
  private static Test monitor = new Test();
  private static Random rand = new Random();
  static String[] typenames = {
    "Pet", "Dog", "Pug", "Cat",
    "Rodent", "Gerbil", "Hamster",
  };
  // Exceptions thrown to console:

  public static void main(String[] args) {
    Object[] pets = new Object[15];
    try {
      Class[] petTypes = {
        Class.forName("c10.Dog"),
        Class.forName("c10.Pug"),
        Class.forName("c10.Cat"),
        Class.forName("c10.Rodent"),
        Class.forName("c10.Gerbil"),
        Class.forName("c10.Hamster"),
      };
      for(int i = 0; i < pets.length; i++)
        pets[i] = petTypes[rand.nextInt(petTypes.length)]
          .newInstance();
    } catch(InstantiationException e) {
      System.out.println("Cannot instantiate");
      System.exit(1);
    } catch(IllegalAccessException e) {
      System.out.println("Cannot access");
      System.exit(1);
    } catch(ClassNotFoundException e) {
      System.out.println("Cannot find class");
      System.exit(1);
    }
    AssociativeArray map =
      new AssociativeArray(typenames.length);
    for(int i = 0; i < typenames.length; i++)
      map.put(typenames[i], new Counter());
    for(int i = 0; i < pets.length; i++) {
      Object o = pets[i];
      if(o instanceof Pet)
        ((Counter)map.get("Pet")).i++;
      if(o instanceof Dog)
        ((Counter)map.get("Dog")).i++;
      if(o instanceof Pug)
        ((Counter)map.get("Pug")).i++;
      if(o instanceof Cat)
        ((Counter)map.get("Cat")).i++;
      if(o instanceof Rodent)
        ((Counter)map.get("Rodent")).i++;
      if(o instanceof Gerbil)
        ((Counter)map.get("Gerbil")).i++;
      if(o instanceof Hamster)
        ((Counter)map.get("Hamster")).i++;
    }
    // List each individual pet:

    for(int i = 0; i < pets.length; i++)
      System.out.println(pets[i].getClass());
    // Show the counts:

    System.out.println(map);
    monitor.expect(new Object[] {
      new TestExpression("%% class c10\\."+
        "(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
        pets.length),
      new TestExpression(
        "%% (Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster)" +
        " : \\d+", typenames.length)
    });
  }
}

   Class.newInstance()使用所选的Class对象生成该类实例。

   IllegalAccessException表示违背Java的安全机制。

   对instanceof有比较严格的限制:只有将器与命名类型进行比较,而不能与Class对象做比较。

   动态的instanceof

   Class.inInstance方法提供了一种动态地调用instanceof约瑟夫的途径。于是所有那些单调的instanceof语句都可以从PetCount的例子一处。

package c10;
import com.bruceeckel.simpletest.*;
import java.util.*;

public class PetCount3 {
  private static Test monitor = new Test();
  private static Random rand = new Random();
  public static void main(String[] args) {
    Object[] pets = new Object[15];
    Class[] petTypes = {
      // Class literals:

      Pet.class,
      Dog.class,
      Pug.class,
      Cat.class,
      Rodent.class,
      Gerbil.class,
      Hamster.class,
    };
    try {
      for(int i = 0; i < pets.length; i++) {
        // Offset by one to eliminate Pet.class:

        int rnd = 1 + rand.nextInt(petTypes.length - 1);
        pets[i] = petTypes[rnd].newInstance();
      }
    } catch(InstantiationException e) {
      System.out.println("Cannot instantiate");
      System.exit(1);
    } catch(IllegalAccessException e) {
      System.out.println("Cannot access");
      System.exit(1);
    }
    AssociativeArray map =
      new AssociativeArray(petTypes.length);
    for(int i = 0; i < petTypes.length; i++)
      map.put(petTypes[i].toString(), new Counter());
    for(int i = 0; i < pets.length; i++) {
      Object o = pets[i];
      // Using Class.isInstance() to eliminate

      // individual instanceof expressions:

      for(int j = 0; j < petTypes.length; ++j)
        if(petTypes[j].isInstance(o))
          ((Counter)map.get(petTypes[j].toString())).i++;
    }
    // List each individual pet:

    for(int i = 0; i < pets.length; i++)
      System.out.println(pets[i].getClass());
    // Show the counts:

    System.out.println(map);
    monitor.expect(new Object[] {
      new TestExpression("%% class c10\\." +
        "(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
        pets.length),
      new TestExpression("%% class c10\\." +
        "(Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster) : \\d+",
        petTypes.length)
    });
  }
}

 

等价性:instanceof与Class

package c10;
import com.bruceeckel.simpletest.*;

class Base {}
class Derived extends Base {}

public class FamilyVsExactType {
  private static Test monitor = new Test();
  static void test(Object x) {
    System.out.println("Testing x of type " +
      x.getClass());
    System.out.println("x instanceof Base " +
      (x instanceof Base));
    System.out.println("x instanceof Derived " +
      (x instanceof Derived));
    System.out.println("Base.isInstance(x) " +
      Base.class.isInstance(x));
    System.out.println("Derived.isInstance(x) " +
      Derived.class.isInstance(x));
    System.out.println("x.getClass() == Base.class " +
      (x.getClass() == Base.class));
    System.out.println("x.getClass() == Derived.class " +
      (x.getClass() == Derived.class));
    System.out.println("x.getClass().equals(Base.class)) "+
      (x.getClass().equals(Base.class)));
    System.out.println(
      "x.getClass().equals(Derived.class)) " +
      (x.getClass().equals(Derived.class)));
  }
  public static void main(String[] args) {
    test(new Base());
    test(new Derived());
    monitor.expect(new String[] {
      "Testing x of type class c10.Base",
      "x instanceof Base true",
      "x instanceof Derived false",
      "Base.isInstance(x) true",
      "Derived.isInstance(x) false",
      "x.getClass() == Base.class true",
      "x.getClass() == Derived.class false",
      "x.getClass().equals(Base.class)) true",
      "x.getClass().equals(Derived.class)) false",
      "Testing x of type class c10.Derived",
      "x instanceof Base true",
      "x instanceof Derived true",
      "Base.isInstance(x) true",
      "Derived.isInstance(x) true",
      "x.getClass() == Base.class false",
      "x.getClass() == Derived.class true",
      "x.getClass().equals(Base.class)) false",
      "x.getClass().equals(Derived.class)) true"
    });
  }
}

instanceof保持了类型的概念,它值的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用==比较实际的Class对象,就没有考虑继承--它或者是这个确切类型,或者不是。

RTTI语法

import com.bruceeckel.simpletest.*;

interface HasBatteries {}
interface Waterproof {}
interface Shoots {}
class Toy {
  
// Comment out the following default constructor

  
// to see NoSuchMethodError from (*1*)

  Toy() {}
  Toy(int i) {}
}

class FancyToy extends Toy
implements HasBatteries, Waterproof, Shoots {
  FancyToy() { super(1); }
}

public class ToyTest {
  private static Test monitor = new Test();
  static void printInfo(Class cc) {
    System.out.println("Class name: " + cc.getName() +
      " is interface? [" + cc.isInterface() + "]");
  }
  public static void main(String[] args) {
    Class c = null;
    try {
      c = Class.forName("FancyToy");
    } catch(ClassNotFoundException e) {
      System.out.println("Can't find FancyToy");
      System.exit(1);
    }
    printInfo(c);
    Class[] faces = c.getInterfaces();
    for(int i = 0; i < faces.length; i++)
      printInfo(faces[i]);
    Class cy = c.getSuperclass();
    Object o = null;
    try {
      
// Requires default constructor:
      o = cy.newInstance();
// (*1*)
    } catch(InstantiationException e) {
      System.out.println("Cannot instantiate");
      System.exit(1);
    } catch(IllegalAccessException e) {
      System.out.println("Cannot access");
      System.exit(1);
    }
    printInfo(o.getClass());
    monitor.expect(new String[] {
      "Class name: FancyToy is interface? [false]",
      "Class name: HasBatteries is interface? [true]",
      "Class name: Waterproof is interface? [true]",
      "Class name: Shoots is interface? [true]",
      "Class name: Toy is interface? [false]"
    });
  }
}

   Class.getInterfaces()方法返回Class对象的数组,这些对象代表的是某个Class对象所包含的接口

   getSuperclass()获取它的直接基类

   使用newInstance()而创建的类必须有一个缺省构造器

   Iprintnfo(),它以一个Class引用为参数,通过getName()获取器名字,并通过isInterface()查看它是否是一个接口

反射:运行时的类信息

   这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。获取了一个指向某个并不在你的程序空间中的对象的引用;事实上,在编译时你的程序根本没法获知这个对象所属的类。

   人们想要在运行时获取类的信息的其中一个动机是:希望提供在跨网络的远程平台上创建和运行对象的能力。称为远程方法调用(RMI),它允许一个Java程序将对象发布在多台机器上。

    Class类支持反射概念,Java附带的库Java.lang.reflect包括了Field、Method以及Constructor类,可以使用Constructor创建新的对象,get()和set()方法读取修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法...

    RTTI与反射之间的真正区别只在于,对RTTI来说,编译器在编译时开始检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时开始检查.class文件

    类方法提取器

    阅读实现了类定义的源代码或是其JDK文档,只能找到在这个类定义中被定义或被覆盖的方法。但对你来说,可能有数十个更有用的方法都是继承基类的。要指出这些方法可能会很乏味且费时。幸运的是,反射机制提供了一种方法。

// Using reflection to show all the methods of a class,
// even if the methods are defined in the base class.

import java.lang.reflect.*;
import java.util.regex.*;

public class ShowMethods {
  private static final String usage =
    "usage: \n" +
    "ShowMethods qualified.class.name\n" +
    "To show all methods in class or: \n" +
    "ShowMethods qualified.class.name word\n" +
    "To search for methods involving 'word'";
  private static Pattern p = Pattern.compile("\\w+\\.");
  public static void main(String[] args) {
    if(args.length < 1) {
      System.out.println(usage);
      System.exit(0);
    }
    int lines = 0;
    try {
      Class c = Class.forName(args[0]);
      Method[] m = c.getMethods();
      Constructor[] ctor = c.getConstructors();
      if(args.length == 1) {
        for(int i = 0; i < m.length; i++)
          System.out.println(
            p.matcher(m[i].toString()).replaceAll(""));
        for(int i = 0; i < ctor.length; i++)
          System.out.println(
            p.matcher(ctor[i].toString()).replaceAll(""));
        lines = m.length + ctor.length;
      } else {
        for(int i = 0; i < m.length; i++)
          if(m[i].toString().indexOf(args[1]) != -1) {
            System.out.println(
              p.matcher(m[i].toString()).replaceAll(""));
            lines++;
          }
        for(int i = 0; i < ctor.length; i++)
          if(ctor[i].toString().indexOf(args[1]) != -1) {
            System.out.println(p.matcher(
              ctor[i].toString()).replaceAll(""));
            lines++;
          }
      }
    } catch(ClassNotFoundException e) {
      System.out.println("No such class: " + e);
    }
  }
} ///:~

   Class的getMethods()和getConstuctor()方法分别发挥Method对象的数组和Constructor对象的数组。

   Class.forName()生成的对象在编译时是不可知的,因此所有的方法特镇签名信息都是在执行时被提取出来的。反射机制提供了足够的支持,使得能够创建一个在编译时完全位置的对象并调用此对象的方法。

阅读(609) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~