运行时类型信息使得你可以在程序运行时发现和使用类型信息
为何需要RTTI
你希望大部分代码尽可能少的了解对象的具体类型,而只是与对象家族的一个通用表示打交道,代码会更易写易读易于维护,设计也更容易实现,所以多态
是面向对象编程的基本目标
但是如果你遇到一个特殊的编程问题——如果你知道某个泛化引用的确切类型,就可以用最简单的方式解决它,该怎么办呢?使用RTTI
可以查询某个对象的具体类型,进行操作
Class对象
每个类都有一个Class
对象,为了生成这个对象,JVM将使用类加载器的子系统
类加载器子系统实际上可以包含一条类加载器链,但只有一个原生类加载器
,是JVM的一部分,加载的是可信类
包括JAVA API类
。通常从本地盘加载,通常不需要添加额外的类加载器,但如果你有特殊需求(以某种特殊方式加载类,以支持Web应用或者在网络中下载类),那么你有一种方式可以挂接额外的类加载器
所有的类都是第一次使用时,动态加载到JVM中的,当程序创建第一个对类的静态成员的引用时就会加载,这证明构造器也是类的静态方法,即使没有使用static
关键字,因此使用new
创建类的新对象也会被当做对类的静态成员的引用
因此,JAVA程序在它开始运行之前并非被完全加载,而是需时加载
类加载器首先检查该类的Class
对象是否已经加载,如尚未加载,就会根据类名查找.class
文件
只要你想在运行时使用类型信息,就必须首先获得对恰当的Class
对象的引用
Class.forName()
是实现此功能的便携途径,传入包含包名的全限定名- 你拥有一个对象,使用
getClass()
方法来获取Class
引用
Class
类中一些有用的方法:
// 全限定类名
public String getName();
// 不含包名的类名
public String getSimpleName();
// 全限定类名
public String getCanonicalName();
// 是否是接口
public boolean isInterface();
// 该类实现的接口
public Class<?>[] getInterfaces();
// 虚拟构造器,允许你正确的创建不知道确切类型的对象,用这种方法创建的类必须带有默认的构造器
public native T newInstance() throws InstantiationException, IllegalAccessException;
// 返回一个Class对象
public static Class<?> forName(String className) throws ClassNotFoundException;
// 获得构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException;
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException;
// 获得所有构造器
public Constructor<?>[] getConstructors();
public Constructor<?>[] getDeclaredConstructors();
// 获得方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException;
public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException;
// 获得所有方法
public Method[] getDeclaredMethods();
public Method[] getMethods();
// 获得所有内部类、接口、枚举
public Class<?>[] getDeclaredClasses();
// 获得域
public native Field getDeclaredField(String name) throws NoSuchFieldException;
// 获得所有域
public native Field[] getDeclaredFields();
getDeclaredXXX()
方法不包含父类的,另一种是包含父类的
类字面常量
Toy.class
这种
使用.class
来创建对Class
对象的引用时,不会自动的初始化该对象
为了使用类而做的准备工作包括三个步骤:
- 加载:由类加载器进行,查找字节码,创建一个
Class
对象 - 链接:验证字节码,为静态域分配存储空间,如果必需将解析这个类创建的对其他类的所有引用
- 初始化:如果该类具有超类,则对其初始化,执行今天初始化器和静态初始化块
初始化被延迟到对静态方法或非常数静态域首次引用时进行
新的转型方法
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b);
其实相当于
House h = (House) b;
对于无法使用普通转型时有用,比如泛型代码,如果存储了Class
引用,可以用来转型
泛化的Class引用
Class intClass = int.class;
intClass = double.class;
普通的类引用是可以的
Class<Integer> intClass = int.class;
intClass = Integer.class;
下面是非法的:
intClass = double.class;
向Class
加入泛型语法可以强制检查
为了使用泛化Class
引用时放松限制,可以使用通配符
Class<?> intClass = int.class;
intClass = double.class;
Class<?>
优于平凡的Class
,表示你就是选择了非具体的版本而非疏忽
也可以限定为类型的任何子类型
Class<? extends Number> bounded = int.class;
bounded = double.class;
bounded = Number.class;
还有个诡异的地方,假设FancyToy
是Toy
的子类
Class<FancyToy> ftClass = FancyToy.class;
FancyToy fancyToy = ftClass.newInstance();
Class<? super FancyToy> up = ftClass.getSuperclass();
Object obj = up.newInstance();
下面是非法的:
Class<Toy> up = ftClass.getSuperclass();
类型转换前先做检查
if (x instanceof Dog) {
((Dog) x).bark();
}
但是如果程序中出现了许多的instanceof
表达式,说明你的设计可能存在瑕疵
反射:运行时的类信息
RTTI
在编译时打开类文件,而对于反射机制,类文件在编译时是不可获取的,运行时打开和检查类文件
Class
类和java.lang.reflect
类库一起对反射进行了支持,包含了Field
、Method
以及Constructor
类,可以用Constructor
创建对象,用get()
和set()
读取和修改与Field
对象关联的字段,用invoke()
方法调用与Method
对象关联的方法
没有任何方式可以阻止反射到达并调用非公共访问权限的方法(包访问权限,私有内部类,private
关键字)
代理与动态代理
待补充