RTTI (Run-Time Type Identification)指程序在运行期间获取对象的类型信息。
我们为什么需要使用 RTTI 呢?
在此之前,先来看一个多态的例子:
abstract class Shape {
void draw() {
System.out.println(this + ".draw()");
}
abstract public String toString();
}
public class Circle extends Shape {
@Override
public String toString() {
return "Circle";
}
public void circle() {
System.out.println("circle method");
}
}
public class Square extends Shape {
@Override
public String toString() {
return "Square";
}
public void square() {
System.out.println("square method");
}
}
public class Triangle extends Shape {
@Override
public String toString() {
return "Triangle";
}
public void triangle() {
System.out.println("triangle method");
}
}
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();
}
}
}
Circle.draw()
Square.draw()
Triangle.draw()
Shape 为 Circle 、Square、Triangle 的基类,代码只对基类的引用进行操作,即使添加新的派生类也无需修改代码。
但上述例子也带来了一个问题:派生类都是通过向上转型成 Shape 存入 List 容器中,从容器中取出对象时,默认也是转换为基类 Shape 类型,故派生类的具体类型会丢失。
倘若我们现在需要根据泛化后 Shape 的具体类型调用不同的方法:Circle 调用 circle()、Square 调用 square() 、Triangle 调用 triangle(),这时 RTTI 就派生用场了,我们可以根据 Class 对象获取对象运行时的各类信息。
接下来,简单说明 RTTI 的工作原理:
每当我们编写并编译一个新类,就会产生一个 Class 对象(保存在同名的 .class 文件中),所有的对象都是基于同名的 Class 对象去创建的。
当程序创建一个对类静态成员的引用时(构造方法也是静态方法,new() 操作符创建对象也被认为是对静态成员的引用),JAVA 虚拟机(JVM)会使用 “类加载器” 去加载 class 文件,一旦某个类的Class 对象被载入内存,它就被用于创建这个类的所有对象。
因此,JAVA 程序并不是在程序开始运行的时候就加载所有对象,其各个部分是在必需时才进行加载。
可以通过下面这个案例来证明:所有的类都是动态加载的。
Class.forName(String className) 在指定某个类的全路径后,可以手动加载其 Class 对象,若加载失败会抛出 ClassNotFoundException 异常。这里更推荐使用类字面常量(.class)的方式去获取 Class 对象的引用(可以参考:类字面常量 .class)。
public class Candy {
static {
System.out.println("Loading Candy");
}
}
public class Gum {
static {
System.out.println("Loading Gum");
}
}
public class Cookie {
static {
System.out.println("Loading Cookie");
}
}
public class SweetShop {
static {
System.out.println("Loading SweetShop");
}
public static void main(String[] args) {
System.out.println("准备加载 Candy");
new Candy();
System.out.println("准备加载 Gum");
try {
Class.forName("mtn.baymax.charpter14.Gum");
} catch (ClassNotFoundException e) {
System.out.println("未找到 Gum 类");
}
System.out.println("准备加载 Cookie");
new Cookie();
}
}
Loading SweetShop
准备加载 Candy
Loading Candy
准备加载 Gum
Loading Gum
准备加载 Cookie
Loading Cookie
进入正题,现在来看看 Class 对象能让我们获取到哪些类型信息:
(1)getName()、getCanonicalName()
获取 Class 的全限定类名(包含包名)
(2)getSimpleName()
获取 Class 的类名(不包含包名)
(3)isInterface()
判断该类是否为接口 interface
(4)getInterfaces()
获得该类所有实现接口的 Class 对象
(5)getSuperclass()
获得继承类的 Class 对象
(6)newInstance()
调用该类的无参构造方法实例化对象(若未提供无参构造方法,会抛出 InstantiationException 异常)
再结合实例,具体了解各类方法的使用情况:
public interface HasBatteries {
}
public interface Shoots {
}
public interface Waterproof {
}
public class Toy {
}
public class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
}
public class ToyTest {
public static void printInfo(Class cc) {
System.out.println("Class Name: " + cc.getName());
System.out.println("is interface? " + "[" + cc.isInterface() + "]");
System.out.println("Simple Name: " + cc.getSimpleName());
System.out.println("Canonical Name: " + cc.getCanonicalName());
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("mtn.baymax.charpter14.FancyToy");
} catch (ClassNotFoundException e) {
System.out.println("无法加载指定类");
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("此类未提供无参构造方法,实例化失败");
System.exit(1);
} catch (IllegalAccessException e) {
System.out.println("无此类的访问权限反射失败");
System.exit(1);
}
printInfo(obj.getClass());
}
}
Class Name: mtn.baymax.charpter14.FancyToy
is interface? [false]
Simple Name: FancyToy
Canonical Name: mtn.baymax.charpter14.FancyToy
Class Name: mtn.baymax.charpter14.HasBatteries
is interface? [true]
Simple Name: HasBatteries
Canonical Name: mtn.baymax.charpter14.HasBatteries
Class Name: mtn.baymax.charpter14.Waterproof
is interface? [true]
Simple Name: Waterproof
Canonical Name: mtn.baymax.charpter14.Waterproof
Class Name: mtn.baymax.charpter14.Shoots
is interface? [true]
Simple Name: Shoots
Canonical Name: mtn.baymax.charpter14.Shoots
Class Name: mtn.baymax.charpter14.Toy
is interface? [false]
Simple Name: Toy
Canonical Name: mtn.baymax.charpter14.Toy
最后让我们回归开头的问题:判断泛化后的 Shape 的具体类型,调用具体类型的不同方法。
稍许改造代码,便能实现需求。
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();
String className = shape.getClass().getName();
switch (className){
case "mtn.baymax.charpter14.Circle":
Circle circle=(Circle)shape;
circle.circle();
break;
case "mtn.baymax.charpter14.Square":
Square square=(Square)shape;
square.square();
break;
case "mtn.baymax.charpter14.Triangle":
Triangle triangle=(Triangle)shape;
triangle.triangle();
break;
}
}
}
}
Circle.draw()
circle method
Square.draw()
square method
Triangle.draw()
triangle method
本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。
若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!