java是如何在让我们在运行时识别对象和和类的信息的。主要有两种方式:一种是传统的RTTI,他假定我们在编译时已经知道了所有的类型信息,另一种是反射机制,它允许我们在运行时发现和使用类的信息
RTTI
abstract classShap{voiddraw(){
System.out.println(this+".draw()");
}abstract publicString toString();
}classCicle extends Shap {
@OverridepublicString toString() {return "Circle";
}
}classSquare extends Shap{
@OverridepublicString toString() {return "Square";
}
}classTriangle extends Shap{
@OverridepublicString toString() {return "Triangle";
}
}public classShaps {public static voidmain(String[] args) {
List shapList = Arrays.asList(new Cicle(),new Square(),newTriangle());for(Shap shap: shapList) {
shap.draw();
}
}
}
运行结果
在这个例子中,当把Shap对象放入List的数组时会向上转型,但在向上转型时也丢失了对象的具体类型信息,对于数组而言,他们只是Shape类的对象。在取出元素时候,将所有的事物都当成object的持有,会自动将结果转为Shape,这就是RTTI最基本的使用形式,因为在java中所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。
Class对象
类是程序的一部分,每个类都有一个Class对象,为了生成这个类的对象,运行这个程序的java的虚拟机将使用被称为“类加载器”的子系统。所有的类都是在对其第一次使用时,动态加载到jvm中。因此Java程序在他开始运行之前并非被完全加载。类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。一旦某个类的Class对象被载入内存,他就被用来创建这个类的所有对象。
classCandy{static{
System.out.println("loading Candy");
}
}classGun{static{
System.out.println("loading Gun");
}
}classCookie{static{
System.out.println("loading Cookie");
}
}public classSweetShop {public static voidmain(String[] args) {
System.out.println("inside main");newCandy();
System.out.println("创建Candy之后");try{
Class.forName("Gum");
}catch(ClassNotFoundException e){
System.out.println("Couldn't find Gum");
}
System.out.println("after Class.forName(\"Gum\")");newCookie();
System.out.println("after creating Cookie");
}
}
运行结果
从输出中可以看出,Class对象仅在需要的时候才被加载,static初始化是在类加载时才进行的。
Class包含很多有有用的方法,下面列举出一下
package demo6;interfaceHasBatteries{}interfaceWaterproof{}interfaceShoots{}classToy{
Toy(){}
Toy(inti){}
}classFancyToy extends Toy implements HasBatteries ,Waterproof,Shoots{
FancyToy(){
super(1);
}
}public classToyTest {static voidprintInfo(Class cc){
System.out.println("class name:"+cc.getName()+ "is interface? [" +cc.isInterface()+ "]");
System.out.println("Simple name:"+cc.getSimpleName());
System.out.println("Canonical name:"+cc.getCanonicalName());
}public static voidmain(String[] args) {
Class c= null;try{
c= Class.forName("demo6.FancyToy");
}catch(ClassNotFoundException e){
System.out.println("Can't not find FancyToy");
System.exit(1);
}
printInfo(c);for(Class face : c.getInterfaces()){
printInfo(face);
}
Class up=c.getSuperclass();
Object obj= null;try{
obj=up.newInstance();
}catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
}catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
运行结果
使用getName来产生全限定的类名,使用getSimpleName()和getCanonicalName()来产生不含包名的类名和全限定的类名。
类字面常量
JAVA还提供了另一种方法来生成对Class对象的引用,即使用类字面常量,例如上面的程序:Facncy.class。 当使用“.class”来创建对Class引用时,不会自动的初始化该Class对象。初始化被延迟到了对静态方法或者非常数静态域进行首次引用时才执行。
package demo6;
import java.util.Random;classInitable{static final int staticFinal = 47;static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);static{
System.out.println("Initializing Initable");
}
}classInitable2{static int staticNonFinal = 147;static{
System.out.println("Initializing Initable2");
}
}classInitable3{static int staticNonFinal = 74;static{
System.out.println("Initializing Initable3");
}
}public classClassInitialization {public static Random rand = new Random(47);public static voidmain(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);
}
}
运行结果
使用.class与发来获得对类的引用不会发生初始化,但是Class.forName()立即就进行了初始化。
如果一个static finall 值是编译期常量,就像Initable.staticFinally那样,那么这个值不需要对类进行初始化就可以被读取,但是如果只是将一个域设置为static finally的,还不足以确保这种行为,例如,对Initable.staticFinally2 的访问将强制进行类的初始化,因为他不是一个编译期常量。
如果一个static域不是finally的,那么在对他访问时,总是要求在它被读取之前,要先为这个域分配存储空间和初始化。