运行时类型信息可以让你在程序运行时发现和使用类型信息。主要用来运行时获取向上转型之后的对象到底是什么具体的类型。
基类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:在运行时,识别一个对象的类型。
在JAVA中,每个类都有一个Class对象,它用来创建这个类的所有对象,换言之,每个类的所有对象都会关联同一个Class对象。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
实际上所有的类都是在对其第一次使用时动态加载到JVM中的,需要用到Class时,类加载器首先会检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找到.class文件。接下来是验证阶段:加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。
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对象是同一个。
1、Class.forName("类名字符串")
2、类名.class
3、实例对象.getClass()
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对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段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,这里没有把基本类型的其他几种全部写上。
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对象的引用。
forName(String name) | 静态方法,传入的参数是一个类的完整类路径的字符串,返回这个类的Class 对象。 |
forName(String name, boolean initialize, ClassLoader loader) | 使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
asSubclass(Class 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");
}
}