湖畔镇

Java——运行时类型信息

运行时类型信息使得你可以在程序运行时发现和使用类型信息

为何需要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对象的引用时,不会自动的初始化该对象

为了使用类而做的准备工作包括三个步骤:

  1. 加载:由类加载器进行,查找字节码,创建一个Class对象
  2. 链接:验证字节码,为静态域分配存储空间,如果必需将解析这个类创建的对其他类的所有引用
  3. 初始化:如果该类具有超类,则对其初始化,执行今天初始化器和静态初始化块

初始化被延迟到对静态方法或非常数静态域首次引用时进行

新的转型方法

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;

还有个诡异的地方,假设FancyToyToy的子类

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类库一起对反射进行了支持,包含了FieldMethod以及Constructor类,可以用Constructor创建对象,用get()set()读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法

没有任何方式可以阻止反射到达并调用非公共访问权限的方法(包访问权限,私有内部类,private关键字)

代理与动态代理

待补充

分享