在开发Java(包含Android)相关程序时, 经常涉及RTTI与反射. RTTI, 即Run-Time Type Identification, 运行时类型识别; 反射, 即Reflect. 两者都是在程序运行时发现和使用类型信息, RTTI在编译时已经获知类型; 反射在运行时才会发现类型.
结合我的编程经验, 分享一些关于RTTI与反射的相关知识, 还有一些在使用时的小技巧和风险点.
RTTIRTTI
RTTI在编译时获知对象的类型, 在运行时识别对象的类型, 应用于多态机制. RTTI支持查询引用所指向对象的确切类型, 即instanceof
方法. Java使用Class对象实现RTTI的功能, Class对象被用于创建一般对象. 当编写并编译类时, 就会产生Class对象, 保存在同名的.class
文件中. 系统使用类加载器(即Java虚拟机, JVM)加载Class对象, 创建实例. 当程序创建第一个类的静态成员引用时, 就会加载这个类. 构造器本质也是类的静态方法, 使用new
创建新对象时, 也会导致加载. Class对象仅仅操作类型, 并不操作实例.
创建类的实例, 和获取Class对象(即调用Class#forName
), 都会加载类.
/**
* 模拟类的加载效果
*
* @author wangchenlong
*/
public class ClassLoader {
/**
* 输出:
*
* Taeyeon is ready!
* Jessica is ready!
*/
public static void main(String[] args) {
// 调用构造器触发类的加载.
new Taeyeon();
try {
// 调用forName获取Class对象的引用, 类如果未被加载, 则加载.
Class.forName("typeinfo.Jessica"); // 使用forName时, 注意添加包名.
} catch (ClassNotFoundException e) {
e.printStackTrace();
Utils.print("Jessica is not here!");
}
new Taeyeon(); // 不会重复执行static段落.
}
}
class Taeyeon {
// static段落, 在类第一次被加载时执行, 只执行一次.
static { Utils.print("Taeyeon is ready!"); }
}
class Jessica {
static { Utils.print("Jessica is ready!"); }
}
Class类中含有大量获取类型信息的静态方法. Class类的newInstance
方法实现虚拟构造器, 根据类型创建对象, 则类需要含有默认构造器(无参构造器), 否则抛出异常.
/**
* 列举Class的静态方法,
* Class的: forName, getInterfaces, getSuperclass, newInstance;
* getName, isInterface, getSimpleName, getCanonicalName.
* Object的getClass.
*
* @author wangchenlong
*/
public class ClassMethods {
public static void showClassInfo(Class<?> clazz) {
if (clazz != null) {
Utils.print("--");
// 输出类型的名称, 和判断是否为接口
Utils.print("ClassName: " + clazz.getName() +
", is interface: " + (clazz.isInterface() ? "yes" : "no"));
// 输出简单类名和标准类名, Canonical即标准的
Utils.print("SimpleName: " + clazz.getSimpleName() +
", CanonicalName: " + clazz.getCanonicalName());
}
}
/**
* --
* ClassName: typeinfo.Tiffany, is interface: no
* SimpleName: Tiffany, CanonicalName: typeinfo.Tiffany
* --
* ClassName: typeinfo.Dance, is interface: yes
* SimpleName: Dance, CanonicalName: typeinfo.Dance
* --
* ClassName: typeinfo.GG, is interface: no
* SimpleName: GG, CanonicalName: typeinfo.GG
*/
public static void main(String[] args) {
// 获取类的类型信息
Class<?> clazz = null;
try {
clazz = Class.forName("typeinfo.Tiffany");
} catch (Exception e) {
Utils.print("Taeyeon is gone!");
return;
}
showClassInfo(clazz); // 输出Class的信息
// 获取接口的类型信息
Class<?>[] clazzs = clazz.getInterfaces(); // 获取类型的接口
if (!Utils.isListEmpty(clazzs)) {
showClassInfo(clazzs[0]); // 输出接口类型信息
}
// 获取父类的类型信息, 通过实例方式
Class<?> upClass = clazz.getSuperclass(); // 获取父类
Object object = null;
try {
object = upClass.newInstance(); // 父类创建对象
} catch (Exception e) {
Utils.print("Super class has no instance.");
return;
}
showClassInfo(object.getClass()); // 获取实例的类型信息
}
}
class Tiffany extends GG implements Dance, Sing {
public Tiffany() {
super("Tiffany");
}
}
class GG {
// Class#newInstance方法, 需要使用默认构造器, 否则无法创建.
public GG() {}
public GG(String name) {}
}
interface Dance {}
interface Sing {}
使用类名的.class
形式, 也可以创建Class对象的引用, 比forName
模式更加明确类型信息, 在编译期检查类型, 但是不会自动初始化Class对象, 延迟到首次引用非常量静态域时进行初始化.
类的创建需要三个步骤: 加载, 链接, 初始化. 查找字节码, 创建Class对象; 验证字节码, 为静态域分配存储空间; 执行父类初始化, 与静态模块初始化.
/**
* 使用.class形式创建Class对象, 延迟初始化Class对象.
* 静态常量也不会触发初始化, 其他静态量和forName会触发初始化
* @author wangchenlong
*/
public class NameClass {
public static Random sRandom = new Random(530);
/**
* 1990 | I'm Yoona First! | 341
* --
* I'm Yoona Second! (Not final) | 1990
* --
* I'm Yoona Third!
*/
public static void main(String[] args) {
Class<Yoona> clazz = Yoona.class; // .class不会触发类的初始化
Utils.print(Yoona.staticFinalInt); // 静态常量不会触发初始化
// 静态非常量(随机量, 非编译期常量, 运行时才获知具体值)会触发初始化
Utils.print(Yoona.staticFinalRand); Utils.printDivider();
Utils.print(Yoona2.staticInt); // 静态非常量会触发初始化
Utils.printDivider();
try {
// forName会触发类的初始化
Class<?> clazz3 = Class.forName("typeinfo.Yoona3");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Utils.print("I don't know where Yoona is.");
}
}
}
class Yoona {
static final int staticFinalInt = 1990; // static并final, 不触发初始化
// static并final, 但不是常量, 数据可变, 非编译期常量, 运行时才获知具体值
static final int staticFinalRand = NameClass.sRandom.nextInt(530);
static { Utils.print("I'm Yoona First!"); }
}
class Yoona2 {
static int staticInt = 1990; // static非final, 触发初始化
static { Utils.print("I'm Yoona Second! (Not final)"); }
}
class Yoona3 {
static { Utils.print("I'm Yoona Third!"); }
}
Class引用指向Class对象, 表示类的类型, 也支持创建类的实例, 包含实例的方法与静态数据. Class支持使用泛型<?>
限定具体类型, 在编译期检查类型, 通配符"?
"表示任何类型. 如Class限定某类数据族, 无法默认转换父类, 需使用extends
, 即<? extends X>
, 表示某类继承于X
类; 同时也支持表示某类是X
类的父类, 使用super
, 即<? super X>
.
RTTI(Run-Time Type Identification, 运行时类型识别)包含两种形式:
- 类型转换, 即"(Class)", 由RTTI保证类型转换正确, 失败则抛出
ClassCastException
. - 对象的类型对象, 即Class对象, 获取运行时信息, 通过
Class#forName
直接获取, 或Object#getClass
间接获取. - 使用
instanceof
关键字, 判断对象是否是类型的实例, 用于向下转型 检查.
类型转换即向下转型, 由父类转换为子类, 在编译期无法得知父类指向的对象是否为子类, 只有在运行期才能得知, 由RTTI确保转换的正确性, 需要显式标记向下转型的类型. 而向上转型不需要显式标记, 因为子类是父类的超集.
统计多个对象的类型数, 调用Class#isInstance
方法, 判断对象是否属于类型.
public void count(Member member) {
for (Map.Entry<Class<? extends Member>, Integer> pair : entrySet()) {
if (pair.getKey().isInstance(member)) {
put(pair.getKey(), pair.getValue() + 1);
}
}
}
或者调用Class#isAssignableFrom
方法, 判断对象是否属于当前类型的子类.
/**
* 递归调用, 判断是否属于当前类型, 一直向上查找至父类型
*
* @param type
*/
private void countClass(Class<?> type) {
Integer times = get(type);
put(type, times == null ? 1 : times + 1);
Class<?> superClass = type.getSuperclass();
if (superClass != null && mBaseType.isAssignableFrom(superClass)) {
countClass(superClass); // 根据父类递归调用
}
}
Class#isInstance
与instanceof
相同, 判断某个实例是否属于某个类型; ==
与equal
相同, 判断某个类型与当前类型是否相同.
public class ClassEqual {
// instanceof 与 isInstance 相同; == 与 equal 相同.
public static void main(String[] args) {
ParkChoAh parkChoAh = new ParkChoAh();
Utils.print("parkChoAh: " + parkChoAh.getClass());
Utils.print("parkChoAh instanceof ParkChoAh: " + (parkChoAh instanceof ParkChoAh));
Utils.print("parkChoAh instanceof AoaMember: " + (parkChoAh instanceof AoaMember));
Utils.print("ParkChoAh.class.isInstance(parkChoAh): " + (ParkChoAh.class.isInstance(parkChoAh)));
Utils.print("AoaMember.class.isInstance(parkChoAh): " + (AoaMember.class.isInstance(parkChoAh)));
Utils.print("parkChoAh.getClass() == ParkChoAh.class: " + (parkChoAh.getClass() == ParkChoAh.class));
// 不能使用==比较, 类型不同无法直接比较
// Utils.print("parkChoAh.getClass() == AoaMember.class: " + (parkChoAh.getClass() == AoaMember.class));
Utils.print("parkChoAh.getClass().equals(ParkChoAh.class)): " + (parkChoAh.getClass().equals(ParkChoAh.class)));
Utils.print("parkChoAh.getClass().equals(AoaMember.class)): " + (parkChoAh.getClass().equals(AoaMember.class)));
}
}
// AOA成员
class AoaMember {}
// 朴草娥, 属于AOA成员
class ParkChoAh extends AoaMember {}
反射
在编译时, 编译器需要获知通过RTTI处理的类实例, 但是在远程调用等其他时候, 编程时无法确定类类实例, 则需要使用反射方式. RTTI在编译时, 确认.class
文件信息; 反射在运行时, 确认.class
文件信息.
使用java.lang.reflect
包内的类与Class类配合, 获取类型的信息. Class#getMethods
获取类型的方法列表, Class#getConstructors
获取类型的构造信息.
public class ShowMethods {
private static Pattern pattern = Pattern.compile("\\w+\\."); // 表示字母与点的组合
public static void showMethods(Class<?> clazz) {
if (clazz == null) return;
Method[] methods = clazz.getMethods(); // 获取方法列表
Constructor<?>[] constructors = clazz.getConstructors(); // 获取构造器列表
for (int i=0; i<methods.length; ++i) {
// 删除所有字母+.的组合, 就是比方法前面的包名全部去掉, 用空代替.
Utils.print(pattern.matcher(methods[i].toString()).replaceAll(""));
}
Utils.printDivider();
for (int i=0; i<constructors.length; ++i) {
Utils.print(pattern.matcher(constructors[i].toString()).replaceAll(""));
}
}
public static void main(String[] args) {
showMethods(MemberCount.class); // 显示方法与构造器信息
}
}
反射在代理模式中有着重要的应用. 代理模式, 通过代理对象调用原对象的方法, 并在方法中添加若干操作, 接口保持相同. 在传统的代理模式中, 代理对象需要与原对象实现相同的接口, 保持接口一致性, 当接口较多时, 代理对象含有较多无用方法. 而通过反射提供动态代理模式, 避免代理对象的方法过多. 通过Method#getName
方法, 筛选被代理方法, 添加额外信息.
/**
* 通过动态代理Handler, 创建代理对象, 实现动态代理
*
* @author wangchenlong
*/
public class SimpleDynamicProxy {
public static void consumer(SomethingInterface iface) {
iface.doSomething();
iface.doSomethingElse();
}
public static void main(String[] args) {
RealObject realObject = new RealObject();
consumer(realObject);
Utils.printDivider();
// 通过动态代理, 创建代理对象
SomethingInterface proxy = (SomethingInterface) Proxy.newProxyInstance(
SomethingInterface.class.getClassLoader(), new Class[] { SomethingInterface.class },
new DynamicProxyHandler(realObject));
consumer(proxy);
}
}
// 动态代理模式
class DynamicProxyHandler implements InvocationHandler {
private Object mProxied; // 代理对象
public DynamicProxyHandler(Object proxied) {
mProxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 筛选方法, 当方法名称为doSomethingElse时, 添加额外信息
if (method.getName().equals("doSomethingElse"))
Utils.print("Proxy: something -> else");
return method.invoke(mProxied, args); // 动态执行方法
}
}
// 代理接口
interface SomethingInterface {
void doSomething();
void doSomethingElse(); // 需要代理对象额外操作的接口
}
// 原对象
class RealObject implements SomethingInterface {
@Override
public void doSomething() {
Utils.print("I want to do something!");
}
@Override
public void doSomethingElse() {
Utils.print("You should do something else!");
}
}
当对象未实例化时, 默认设置为null
, 当null
执行方法时, 产生NullPointerException
, 即空指针异常. 如果引入空对象替换null
, 即可避免异常问题, 也易于统一管理. 空对象是类中的属性均相同, 且为空. 动态代理模式支持创建含有相同接口的空对象, 简化创建逻辑.
public class NullMember {
// ClassLoader任意选择, 代理接口, 空对象Handler
public static AoaInterface newNullMember(Class<? extends AoaInterface> type) {
return (AoaInterface) Proxy.newProxyInstance(AoaInterface.class.getClassLoader(),
new Class[] { Null.class, AoaInterface.class }, new NullAoaProxyHandler(type));
}
public static void main(String[] args) {
// 真实对象和空对象做对比
AoaInterface[] members = { new ParkChoAhAoa(), newNullMember(ParkChoAhAoa.class) };
for (AoaInterface member : members) {
AoaInterface.Test.test(member);
Utils.printDivider();
}
}
}
// 朴草娥
class ParkChoAhAoa implements AoaInterface {
@Override
public String name() {
return "朴草娥";
}
@Override
public List<Skill> skills() {
return Arrays.asList(new Skill() {
@Override
public String description() {
return "Dancing, Dancing";
}
}, new Skill() {
@Override
public String description() {
return "Singing, Singing";
}
});
}
}
/**
* 空对象的代理Handler, 用于创建实现AoaMember接口的空对象
*/
class NullAoaProxyHandler implements InvocationHandler {
private String mNullName;
private AoaInterface mNProxied = new NAoaInterface(); // 代理对象始终设置为空对象
public NullAoaProxyHandler(Class<? extends AoaInterface> clazz) {
mNullName = clazz.getSimpleName() + " Null AoaMember"; // 空对象名称
}
private class NAoaInterface implements Null, AoaInterface {
@Override
public String name() {
return mNullName;
}
// 列表的空对象, 容器的默认空对象
@Override
public List<Skill> skills() {
return Collections.emptyList();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(mNProxied, args); // 使用空的代理对象调用方法
}
}
interface Null {
}
interface Skill {
String description();
}
interface AoaInterface {
String name();
List<Skill> skills();
// 接口内部的测试类
class Test {
public static void test(AoaInterface member) {
if (member instanceof Null)
Utils.print("Null Member");
Utils.print("Name: " + member.name());
for (Skill skill : member.skills())
Utils.print("Skill" + skill.description());
}
}
}
OK, that's all! Enjoy it!
网友评论