美文网首页程序员
Java 运行时类型识别 与 反射

Java 运行时类型识别 与 反射

作者: SpikeKing | 来源:发表于2017-06-14 15:31 被阅读283次

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

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

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

    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<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, 运行时类型识别)包含两种形式:

    1. 类型转换, 即"(Class)", 由RTTI保证类型转换正确, 失败则抛出ClassCastException.
    2. 对象的类型对象, 即Class对象, 获取运行时信息, 通过Class#forName直接获取, 或Object#getClass间接获取.
    3. 使用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#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<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!

    相关文章

      网友评论

        本文标题:Java 运行时类型识别 与 反射

        本文链接:https://www.haomeiwen.com/subject/iiasqxtx.html