我们都知道对于面向对象的语言,一般都会在编译期执行类型检查。那如果我们要在运行期间如何对数据类型进行提取并识别那,在Java里面有两种方式可以做到这一点。其一,利用传统的RTTI,它假定我们在编译时就已经知道所有类型。另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
RTTI(运行时类型识别)
RTTI的一般形式
我们先来看一段代码
abstract class Shape {
void draw() { System.out.println(this + ".draw()"); }
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 class Shapes {
public static void main(String[] args) {
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle()
);
for(Shape shape : shapeList)
shape.draw();
}
}
在这个例子中,当Shape对象存入到List<Shape>的数组时会向上转型。但是向上转型为Shape的时候会丢失具体类型。对于数组来说,它们都是Shape类型,这里要特殊说明一下,当数组的元素被取出的过程中实际的过程是它会把所有的事物当做Object持有,之后会发生结果转型,转回Shape的过程。这也正是RTTI最经典的使用方式
同时,也说明一点,在这里RTTI类型转换并没有彻底:Object被转型为Shape,而不是转型为Circle、Square或者Triangle。之后Shape具体执行哪种代码,就交给多态机制操作
这也是我们代码编写过程中惯用的方法,尽可能少的了解对象的具体类型,只与一个通用的窗口打交道。这种写法也便于理解项目同时,也更容易维护。
RTTI实现机制
RTTI功能的实现是由Class对象来完成的,它包含了类的所有相关信息,而这个对象是在编写并编译一个新的Java类时产生的。而且这个对象只有在其使用时才会被加载。那我们该如何获取这个Class对象那,其实他和普通对象一样,通过以下三种方式来获取,
方式1:通过Object类的getObject()方法
Person p = new Person();
Class c = p.getClass();
方式2: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)
Class c2 = Person.class;
方式3: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)
Class c3 = Class.forName("Person");
这三种方式中,一般会选择第三种方式,因为在你获取恰当的Class对象的引用时,不需要像前两种方式需要拥有该类型对象
当你获取到Class对象之后,你就可以使用它来获取你想要的了解的类型的所有信息,包括创建对象。
Class是如何创建对象的,大家先看以下代码:
import static net.mindview.util.Print.*;
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 {
static void printInfo(Class cc) {
print("Class name: " + cc.getName() +
" is interface? [" + cc.isInterface() + "]");
print("Simple name: " + cc.getSimpleName());
print("Canonical name : " + cc.getCanonicalName());
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("typeinfo.toys.FancyToy");
} catch(ClassNotFoundException e) {
print("Can't find FancyToy");
System.exit(1);
}
printInfo(c);
for(Class face : c.getInterfaces())
printInfo(face);
Class up = c.getSuperclass();
Object obj = null;
try {
// Requires default constructor:
obj = up.newInstance();
} catch(InstantiationException e) {
print("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
print("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
可以通过newInstance()方法来创建对象,该方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:“我不知道你的确切类型,但是无论如何要正确地创建”。在这段代码中,up仅仅只是一个Class引用,但这个引用通过该方法可以成功的指向Toy对象。如果在实际生活中,应用这个方法,你需要很清楚的知道这个Class对象到底是什么,并能够执行什么转型。另外,在对象正确创建的过程中,必须是使用的默认构造器,不能使用其他构造器。这是该方法出现的缺点。