运行时类型信息可让你在程序运行时发现和使用类型信息。java
在Java中运行时识别对象和类的信息有两种方式:传统的RTTI,以及反射。下面就先来讲下RTTI。编程
一、RTTI:数组
RTTI:在运行时,识别一个对象的类型。可是这个类型在编译时必须已知。安全
下面经过一个例子来看下RTTI的使用。这里涉及到了多态的概念:让代码只操做基类的引用(面向对象编程中基本的目的),而实际上调用具体的子类的方法,一般会建立一个具体的对象(Circle,Square,或者Triangle,见下例),把它向上转型为Shape(忽略了对象的具体类型),并在后面的程序中使用匿名(即不知道具体类型)的Shape引用:服务器
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(); } }
输出结果为:dom
Circle.draw()
Square.draw()
Triangle.draw()
存入数组的时候,会自动向上转型为Shape,丢失了具体的类型,当从数组中取出的时候,(List容器将全部的事物都当作Object持有),会自动将结果转型回Shape,这就是RTTI的基本用法。测试
Java中全部的类型转换都是在运行时进行正确性检查的,也就是RTTI:在运行时,识别一个对象的类型。this
上面的转型并不完全,数组的元素取出时由Object转型为Shape,而不是具体的类型。这是由于目前咱们只知道这个List<Shape>保存的都是Shape。编译时这是由容器和Java泛型系统来确保这一点的,而在运行时由类型转换操做来确保这一点的。spa
而可以经过Shape对象执行到子类的具体代码就是由多态来决定的了,具体看Shape引用所指向的具体对象。.net
另外,使用RTTI,能够查询某个Shape引用所指向的对象的确切类型,而后选择性的执行子类的方法。
二、Class对象:
要了解RTTI在Java中的工做原理,必须知道类型信息在运行时是如何表示的,这里是由Class这个特殊对象完成的。
Class对象是用来建立类的全部的“常规”对象的。Java使用Class对象来执行其RTTI。
每当编译一个新类,就会产生一个Class对象(.class文件)。运行这个程序的JVM将使用“类加载器”这个子系统。
类加载器子系统:包含一条类加载器链,但只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载可信类,包括Java API类,一般是从本地磁盘加载的。当须要以某种特定的方式加载类,以支持Web服务器应用,能够挂接额外的类加载器。
2.一、加载类的时机:
当程序建立第一个对类的静态成员的引用时,就会加载这个类。这证实其实构造器也是类的静态方法,当使用new操做符建立类的新对象也会当作对类的静态成员的引用。
可见Java程序时动态加载的,按需加载。须要用到Class时,类加载器首先会检查这个类的Class对象是否已经加载,若是还没有加载,默认的类加载器就会根据类名查找到.class文件。接下来是验证阶段:加载时,它们会接受验证,以确保其没有被破坏,而且不包含不良Java代码。
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.三、类字面常量:
除了使用forName()方法,Java还提供了另外一种方法来生成对Class对象的引用,即便用类字面常量:
NewLetter.class;
此方法简单安全,编译时就受到检查,更高效。不只可用于普通类,也能够用于接口,数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE,TYPE字段是一个引用,执行对应的基本数据类型的Class对象。为了统一,建议都使用.class这种形式。
2.四、使用.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.五、下面是判断是否执行初始化的一些状况:
(1).class语法得到对类的引用不会引起初始化;
(2)Class.forName()产生了Class引用,当即进行了初始化;
(3)若是一个static final值是“编译器常量”,那么这个值不须要对类进行初始化就能够被读取;
(4)若是只是把一个域设置为static final还不足以确保这种行为,例如上面的:
static final double b = Math.random();
(5)若是一个static域但不是final的,那么在对它访问时,老是要先进行连接(为这个域分配存储空间)和初始化(初始化该存储空间);
2.六、泛化的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.一、使用通配符?放松泛型的限定:
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.二、泛型下的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.三、新的转型语法:cast()方法
直接看下代码:
Animal animal = new Dog(); Class<Dog> dogCls = Dog.class; Dog dog = dogCls.cast(animal); // 或者直接使用下面的转型方法 dog = (Dog)animal;
能够发现,使用cast()方法的作了额外的工做,这种转换方法能够用在如下的状况中:在编写泛型代码的时候,若是存储了Class引用,并但愿之后经过这个Class引用来执行转型,就可使用cast()方法。
三、类型检查
3.一、类型转换前先作检查
编译器容许你自由的作向上转型的赋值操做,而不须要任何显示的转型操做,就好像给超类的引用赋值那样。
然而若是不使用显示的类型转换,编译器就不容许你执行向下转换赋值,这个时候咱们不妨先来检查一下对象是否是某个特定类型的实例,使用到了关键字 instanceof:
if(x instanceof Dog) ((Dog) x).bark();
3.二、RTTI的形式:
因此,到目前为止,咱们知道RTTI的形式包括:
(1)传统的类型转换 (Shape)
(2)表明对象的类型的Class对象
(3)关键字instanceof
3.三、动态的instanceof方法:
Class.isInstance方法提供给了一种动态测试对象的途径。
下面演示下 instanceof 和 Class.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\n", c.toString(), c instanceof Shape); // 判断是否Circle的实例 System.out.format("Using instanceof: %s is a circle? %b\n", c.toString(), c instanceof Circle); // 判断是否超类的实例 System.out.format("Using Class.isInstance: %s is a shape? %b\n", c.toString(), Shape.class.isInstance(c)); // 判断是否接口的实例 System.out.format("Using Class.isInstance: %s is a Attribute? %b\n", 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.四、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