Java 运行时类型识别 与 反射

欢迎Follow我的GitHub, 关注我的.

在开发Java(包含Android)相关程序时, 经常涉及RTTI与反射. RTTI, 即Run-Time Type Identification, 运行时类型识别; 反射, 即Reflect. 两者都是在程序运行时发现和使用类型信息, RTTI在编译时已经获知类型; 反射在运行时才会发现类型.

结合我的编程经验, 分享一些关于RTTI反射的相关知识, 还有一些在使用时的小技巧和风险点.

Java 运行时类型识别 与 反射_第1张图片
RTTI

RTTI

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 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, 即, 表示某类继承于X类; 同时也支持表示某类是X类的父类, 使用super, 即.

RTTI(Run-Time Type Identification, 运行时类型识别)包含两种形式:

  1. 类型转换, 即"(Class)", 由RTTI保证类型转换正确, 失败则抛出ClassCastException.
  2. 对象的类型对象, 即Class对象, 获取运行时信息, 通过Class#forName直接获取, 或Object#getClass间接获取.
  3. 使用instanceof关键字, 判断对象是否是类型的实例, 用于向下转型 检查.

类型转换即向下转型, 由父类转换为子类, 在编译期无法得知父类指向的对象是否为子类, 只有在运行期才能得知, 由RTTI确保转换的正确性, 需要显式标记向下转型的类型. 而向上转型不需要显式标记, 因为子类是父类的超集.

统计多个对象的类型数, 调用Class#isInstance方法, 判断对象是否属于类型.

public void count(Member member) {
    for (Map.Entry, 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#isInstanceinstanceof相同, 判断某个实例是否属于某个类型; ==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

反射在代理模式中有着重要的应用. 代理模式, 通过代理对象调用原对象的方法, 并在方法中添加若干操作, 接口保持相同. 在传统的代理模式中, 代理对象需要与原对象实现相同的接口, 保持接口一致性, 当接口较多时, 代理对象含有较多无用方法. 而通过反射提供动态代理模式, 避免代理对象的方法过多. 通过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 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 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 clazz) {
        mNullName = clazz.getSimpleName() + " Null AoaMember"; // 空对象名称
    }

    private class NAoaInterface implements Null, AoaInterface {

        @Override
        public String name() {
            return mNullName;
        }

        // 列表的空对象, 容器的默认空对象
        @Override
        public List 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 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!

你可能感兴趣的:(Java 运行时类型识别 与 反射)