面试|你了解RTTI吗

本文介绍了Java中的RTTI(运行时类型识别),包括其作用、如何在运行时识别类型信息,以及Class对象在JVM内存中的表示。通过示例代码展示了Class.forName(), getClass()和类字面常量等获取Class对象引用的方法。" 106739764,7762070,Element-UI DateTimePicker在不同浏览器的问题及解决方案,"['前端开发', 'Vue', 'Element-ui']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

运行时识别对象和类的信息主要有两种方式:一种是“传统的”RTTI,它默认我们在编译时已经知道了所有的类型;一种是反射,它允许我们在运行时发现和使用类的信息。

今天首先来认识下RTTI。

1.什么是RTTI以及为什么需要RTTI

RTTI的英文全称是Run-Time Type Identification,即运行时类型识别。它可以在程序运行时检查父类型的引用是否可以指向子类型的对象,即确保类型向上转换安全
示例1:

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();
		}
	}
}

运行结果:

Circle.draw()
Square.draw()
Triangle.draw()

在Java中,所有的类型转换都是在运行时进行正确性检查的。对于编译器来讲,它只知道容器类放入的是Shape父类引用,而不知引用指向的具体对象是什么对象,那么这个信息的确认就可以通过RTTI来保证,即找到Shape父类引用指向的对象类型。
那么RTTI识别父类引用指向的对象类型这个功能有什么用呢?
假设我们需要对Square类和Triangle类做其他操作,而当识别到Circle类时就跳过,那么识别对象类型就非常有必要了。

2.既然RTTI的主要作用是在运行时识别类型信息,那么类型信息在运行时是如何表示的呢?

我们知道编译器会根据Java代码构建抽象语法树,然后编译生成字节码,表现出来就是一个十六进制的.class文件;之后,随着程序启动JVM也跟着启动,在需要类的时候加载.class文件到JVM内存进行一系列的工作。这里一个.class文件其实就是一个类,那么要想弄明白类型信息在运行时是如何表示的,就需要弄明白.class文件加载到JVM内存之后是如何表示的。
在这里,JVM会把.class文件当作一个Class类的对象,JVM需要使用某个类的时候,首先由类加载器加载该类的Class对象,即.class文件,然后由该Class对象完成类对象的构造。下面用例子证明类的Class对象被载入JVM内存后,它就被用来创建这个类的所有对象:示例2

class A {
	static { System.out.println("A static");}
}
class B {
	static { System.out.println("B static");}
}
class C {
	static { System.out.println("C static");}
}
public class Test1 {
	public static void main(String[] args) {
		new A();
		try {
			Class<?> cs = Class.forName("com.starry.rtti.B");   // B类的全路径类名
   System.out.println(cs.toString());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		new C();
	}
}

这里需要解释下这行语句:

Class<?> cs = Class.forName("com.starry.rtti.B");

之前说过,每个类都有一个Class对象,而forName(arg)方法是取得arg类的Class对象的引用;示例2中是获取B类的Class对象的引用;这里的输出结果是:

A static
B static
class com.starry.rtti.B
C static

结果表明:①forName()方法的调用会使得类B加载到JVM内存;②返回B类的Class对象的引用,并打印引用,即class和引用的名字;(感兴趣的可以查看Class类的toString()方法源码)

这里Class对象的引用cs非常重要,它是你在运行时时使用类型信息的唯一途径。当然,获取类B的Class对象的引用还有一种方式,即通过B类对象的方法getClass():示例3

A a = new A();
Class<?> cs1 = a.getClass();

注意:getClass()方法是Object类的一个本地方法。

Java里面还提供一种方法来获取类的Class对象的引用:类字面常量

Class<?> cs2 = A.class;
System.out.println(cs2.toString());

这种方式非常简单高效,不需要对象,也不需要捕获异常,所以比较推荐使用这种方式。但是如果你实际操作过的话,你会发现这种方式并不能自动化地初始化该Class对象。下面写个示例来证明下:示例4

class Initable {
	static final int staticFinal = 10;
	static final int staticFinal2 = ClassInitialization.random.nextInt(100);
	static {
		System.out.println("Initializing Initable");
	}
}
class Initable2 {
	static int staticNonFinal = 100;
	static {
		System.out.println("Initializing Initable2");
	}
}
class Initable3 {
	static int staticNonFinal = 11;
	static {
		System.out.println("Initializing Initable3");
	}
}
public class ClassInitialization {
	public static Random random = new Random(100);
	public static void main(String[] args) throws Exception {
		Class<?> initable = Initable.class;
		System.out.println("创建Initable类的class对象引用后...");
		System.out.println(Initable.staticFinal);
		System.out.println(Initable.staticFinal2);
		System.out.println("======================");
		System.out.println(Initable2.staticNonFinal);
		System.out.println("======================");
		Class<?> initable3 = Class.forName("com.starry.rtti.Initable3");
		System.out.println("创建Initable3类的class对象引用后....");
		System.out.println(Initable3.staticNonFinal);
	}
}

运行结果:

创建Initable类的class对象引用后...
10
Initializing Initable
15
======================
Initializing Initable2
100
======================
Initializing Initable3
创建Initable3类的class对象引用后....
11

结果进一步表明:通过Initable.class获取Class对象的引用并不能初始化Initable类;如果不能理解第二行和第三行的输出结果,可以看这篇文章

上面介绍了获取Class对象引用的三种方法,即识别运行时类的信息:

  • Class.forName(arg)方法;
  • 对象的.getClass()方法;
  • 类字面常量;
    获取到Class对象的引用,就能在运行时获取类性信息。多态中,父类的引用可以指向子类的对象,运行过程中,子类对象的Class对象包含父类的信息,因此可以安全的向上转型为父类的引用。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值