RTTI(运行时类型信息):
运行时类型信息可以让你在程序运行时发现和使用类型信息。主要用来运行时获取向上转型之后的对象到底是什么具体的类型。
如下实例:
上方类图分析
基类Vehicle位于顶部,Car、Bus、Bike 3个派生类向下扩展,面向对象编程很重要的目的是:让代码只操作基类的引用,就可以达到我们想要的效果,而不必去直接操作派生类;如果我们想再增加一个派生类 MotorBike,则直接对此类进行扩展即可,无需改变原来的代码逻辑。
Vehicle类
-
abstract
class
Vehicle
-
{
-
abstract void run();
-
}
Car类
-
public
class Car extends Vehicle
-
{
-
-
@Override
-
void run()
-
{
-
System.out.println(
"小汽车开动");
-
}
-
-
}
Bus类
-
public
class Bus extends Vehicle
-
{
-
-
@Override
-
void run()
-
{
-
System.out.println(
"公交车开动");
-
}
-
-
}
Bike类
-
public
class Bike extends Vehicle
-
{
-
-
@Override
-
void run()
-
{
-
System.out.println(
"自行车骑行");
-
}
-
-
}
调用
-
public static void main(String[] args)
-
{
-
//均向上转型
-
Vehicle vehicle1 =
new Car();
-
vehicle1.run();
-
Vehicle vehicle2 =
new Bus();
-
vehicle2.run();
-
Vehicle vehicle3 =
new Bike();
-
vehicle3.run();
-
}
输出
小汽车开动
公交车开动
自行车骑行
从上面的代码可以看到,我们无需直接调用派生类,只要将派生类的实例向上转型为基类,然后通过基类来调用(派生类会丢失具体的类型)。
有个要注意的问题,不能向下转型调用,如:
-
public
class BYDCar extends Car
-
{
-
@Override
-
void run()
-
{
-
System.out.println(
"BYD小汽车开动");
-
}
-
}
-
public static void main(String[] args)
-
{
-
//BYDCar是Car的子类,这里用了向下转型
-
BYDCar bydCar =(BYDCar)
new Car();
-
bydCar.run();
-
}
BYDCar是Car的子类,代码里用了向下转型,编译是通过的,不会报错,但运行会报错:
Exception in thread "main" java.lang.ClassCastException: rtti.Car cannot be cast to rtti.BYDCar
at rtti.Test1.main(Test1.java:11)
Java中所有的类型转换都是在运行时进行正确性检查的,也就是RTTI:在运行时,识别一个对象的类型。
Class对象
在JAVA中,每个类都有一个Class对象,它用来创建这个类的所有对象,换言之,每个类的所有对象都会关联同一个Class对象。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
实际上所有的类都是在对其第一次使用时动态加载到JVM中的,需要用到Class时,类加载器首先会检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找到.class文件。接下来是验证阶段:加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。
实例来看Class对象的加载
-
package rtti;
-
-
public
class A
-
{
-
static {
-
System.
out.println(
"A is load");
-
}
-
}
-
package rtti;
-
-
public
class
Test2
-
{
-
public static void main(String[] args)
-
{
-
System.
out.println(
"before load");
-
new A();
-
System.
out.println(
"after load");
-
-
System.
out.println(
"before load1");
-
try {
-
Class a=Class.forName(
"rtti.A");
-
}
catch (ClassNotFoundException e) {
-
System.
out.println(
"Couldn't find A");
-
}
-
System.
out.println(
"after load1");
-
}
-
}
输出如下
before load
A is load
after load
before load1
after load1
从输出中可以看到,static模块在类第一次被加载时候被执行,Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
如何验证同一个类的多个对象的Class对象是一个呢?
可以用A的Class对象与A的实例的对象的Class对象进行比较( == ),因为==用来比较引用是否相同。
-
public
static void main(
String[] args)
-
{
-
A a = new A();//A的实例对象
-
Class clazz = A.class;//A的Class对象
-
Class clazz1 = a.getClass();//实例对象a的Class对象
-
System.out.println(clazz==clazz1);
-
}
运行后结果是true,所以说明同一个类的多个对象的Class对象是同一个。
获取 Class对象
1、Class.forName("类名字符串")
2、类名.class
3、实例对象.getClass()
Class.forName
forName是取得Class对象引用的一种方法,传入一个类的完整类路径也可以获得Class 对象,如果找不到你想要加载的类,就会抛出ClassNotFoundException异常,所以用try、catch来捕获这个方法可能抛出的ClassNotFoundException异常。
-
public static void main(String[] args)
-
{
-
try {
-
Class a=Class.forName(
"rtti.A");
-
System.
out.println(a);
-
}
catch (ClassNotFoundException e) {
-
System.
out.println(
"Couldn't find A");
-
}
-
}
输出:class rtti.A
类名.class
这种方式叫类字面常量,这样来获取不仅更简单,还更安全,因为它在编译时就会检查。
字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下:
boolean.class | Boolean.TYPE |
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
-
public static void main(String[] args)
-
{
-
try {
-
Class a=Class.forName(
"rtti.A");
-
Class a1 = A.
class;
-
System.
out.println(a==a1);
-
-
System.
out.println(Integer.TYPE==int.
class);
-
System.
out.println(
Boolean.TYPE==boolean.
class);
-
System.
out.println(
Double.TYPE==double.
class);
-
System.
out.println(
Void.TYPE==void.
class);
-
-
}
catch (ClassNotFoundException e) {
-
System.
out.println(
"Couldn't find A");
-
}
-
}
全部输出是true,这里没有把基本类型的其他几种全部写上。
实例对象.getClass()
-
public static void main(String[] args)
-
{
-
try {
-
Class clazz1=Class.forName(
"rtti.A");
-
Class clazz2 = A.class;
-
A a =
new A();
-
Class clazz3 = a.getClass();
//getClass 方式
-
System.
out.println(clazz1==clazz2);
-
System.
out.println(clazz1==clazz3);
-
-
}
catch (ClassNotFoundException e) {
-
System.
out.println(
"Couldn't find A");
-
}
-
}
上面3种方法都是可以得到Class对象的引用。
Class的常用方法
forName(String name) | 静态方法,传入的参数是一个类的完整类路径的字符串,返回这个类的Class 对象。 |
forName(String name, boolean initialize, ClassLoader loader) | 使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
asSubclass(Class<U> clazz) | 强制转换该 Class 对象,以表示指定的 class 对象所表示的类的一个子类。 |
cast(Object o) | 这个方法是将传入的对象强制转换成Class 对象所代表的类型的对象。 |
getCanonicalName() | 返回 Java Language Specification 中所定义的底层类的规范化名称。 |
getClasses() | 返回一个包含某些 Class 对象的数组,这些对象表示属于此 Class 对象所表示的类的成员的所有公共类和接口。 |
getClassLoader() | 返回该类的类加载器。 |
getConstructor(Class<?>... parameterTypes) | 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 |
getConstructors() | 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。 |
getFields() | 获取Class对象的所有公有成员属性的java.lang.reflect.Field数组。 |
getField(String name) | 按照字段名称获取公有字段的Field对象,注意name是区分大小写的。 |
getDeclaredFields() | 获取Class对象的所有成员属性的Field数组; |
getDeclaredField(String name) | 按照字段名称获取所有字段的Field 对象,注意name是区分大小写的。 |
getMethods() | 获取Class 对象的公有方法(以及从父类继承的方法,但不包含构造方法)的java.lang.reflect.Method数组 |
getMethod(String name,Class<?> …parameterTypes) | 按照name 指定的方法名称,parameterTypes 指定的可变数组获取公有方法(以及从父类继承的方法,但不包含构造方法)的Method对象,注意name 是区分大小写的。 |
getDeclaredMethods() | 获取Class 对象的所有方法(不包含父类继承的方法,构造方法)的Method数组。 |
getDeclaredMethod(String name,Class<?> …parameterTypes) | 按照name指定的方法名称,parameterTypes 指定的可变数组获取所有方法(不包含父类继承的方法,构造方法)的Method对象,注意name 是区分大小写的。 |
getName() | 以String 形式返回Class 对象所表示的实体的完整类名,基本数据类型 返回自身,数组类型(以String 数组为例)返回[Ljava.lang.String;,这个方法没有 getCanonicalName()返回的完整,但不是所有的类型都有底层的规范化名称。 |
getPackage() | 以java.lang.reflect.Package形式返回Class对象的类所在的包,基本数 据类型、数组抛出异常。 |
getResource(String url) | 查找带有给定名称的资源 |
-
public
class
A
-
{
-
int a=
0;
-
int b=
1;
-
-
public void say()
-
{
-
System.
out.println(
"say");
-
}
-
-
public void hello()
-
{
-
System.
out.println(
"hello");
-
}
-
}
-
public static void main(String[] args)
-
{
-
try {
-
Class clazz1=Class.forName(
"rtti.A");
-
-
System.
out.println(clazz1.getName());
//打印类名
-
System.
out.println(clazz1.getSimpleName());
//获取类名(不包括包名)
-
System.
out.println(clazz1.getCanonicalName());
//获取类名(包括包名)
-
System.
out.println(
"------------------------");
-
//获取自身的属性
-
for (Field f : clazz1.getDeclaredFields()) {
-
System.
out.println(f.getName());
-
}
-
System.
out.println(
"------------------------");
-
//方法,包含继承来的方法
-
for (Method m : clazz1.getMethods()){
-
System.
out.println(m.getName());
-
}
-
-
-
}
catch (ClassNotFoundException e) {
-
System.
out.println(
"Couldn't find A");
-
}
-
}
关注下方公众号,有更多资料、实例代码、面试技巧奉上!
转载:https://blog.csdn.net/dkm123456/article/details/115732790