zoukankan      html  css  js  c++  java
  • Java系列笔记(2)

    目录

    1. 前言
    2. 传统的RTTI
    3. 反射
    4. 反射的实现方式
    5. 反射的性能
    6. 反射与设计模式

    前言

    并不是所有的Class都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程,),这就是RTTI(Runtime Type Information,运行时类型信息)。

    在java中,有两种RTTI的方式,一种是传统的,即假设在编译时已经知道了所有的类型;还有一种,是利用反射机制,在运行时再尝试确定类型信息。

    本文主要讲反射方式实现的RTTI,建议在阅读本文之前,先了解类的加载机制(参考我的博客:Java系列笔记(1) - Java 类加载与初始化)。

    在本文中,将共同使用下面的玩具类Toy,该类中定义了公有、私有方法,变量,构造方法,父类、父接口等:

    复制代码
    package myblog.rtti;
    
    /**
     * @project MyBlog
     * @create 2013年6月28日 下午4:42:46
     * @version 1.0.0
     * @author 张广
     */
    public interface IToy {
            public String playToy(String player) throws Exception;
    }
    复制代码
    复制代码
    package myblog.rtti;
    
    public class AbstractToy implements IToy {
            @Override
            public String playToy(String player) throws Exception {
                    System.out.println(player + " plays abstract toy");
                    return "";
            }
    }
    复制代码
    复制代码
    package myblog.rtti;
    
    public class Toy extends AbstractToy {
    
            private String name;
    
            public String color;
    
            protected int size;
    
            public static final int price = 10;
    
            static {
                    System.out.println("Loading");
            }
    
            public Toy() {// 构造方法一定要声明为public类型,不然用getConstructors无法得到
                    System.out.println("Initialing");
                    setName("myToy");
                    color = "red";
                    size = 5;
            }
    
            public Toy(String name, String color, int size) {
                    this.setName(name);
                    this.color = color;
                    this.size = size;
            }
    
            public String getName() {
                    return name;
            }
    
            public void setName(String name) {
                    this.name = name;
            }
    
            @Override
            public String playToy(String player) throws Exception {
                    String msg = buildMsg(player);
                    System.out.println(msg);
                    return msg;
            }
    
            private String buildMsg(String player) {
                    String msg = player + " plays " + name;
                    return msg;
            }
    }
    复制代码

    传统的RTTI

    严格的说,反射也是一种形式的RTTI,不过,一般的文档资料中把RTTI和反射分开,因为一般的,大家认为RTTI指的是传统的RTTI,通过继承和多态来实现,在运行时通过调用超类的方法来实现具体的功能(超类会自动实例化为子类,或使用instance of)。

    传统的RTTI有3种实现方式:

    • 向上转型或向下转型(upcasting and downcasting),在java中,向下转型(父类转成子类)需要强制类型转换
    • Class对象(用了Class对象,不代表就是反射,如果只是用Class对象cast成指定的类,那就还是传统的RTTI)
    • instanceof或isInstance()

    传统的RTTI与反射最主要的区别,在于RTTI在编译期需要.class文件,而反射不需要。传统的RTTI使用转型或Instance形式实现,但都需要指定要转型的类型,比如:

    public void rtti(Object obj){
        Toy toy = Toy(obj);
        // Toy toy = Class.forName("myblog.rtti.Toy")
        // obj instanceof Toy
    }

    注意其中的obj虽然是被转型了,但在编译期,就需要知道要转成的类型Toy,也就是需要Toy的.class文件。

    相对的,反射完全在运行时在通过Class类来确定类型,不需要提前加载Toy的.class文件。

    反射

    那到底什么是反射(Reflection)呢?反射有时候也被称为内省(Introspection),事实上,反射,就是一种内省的方式,Java不允许在运行时改变程序结构或类型变量的结构,但它允许在运行时去探知、加载、调用在编译期完全未知的class,可以在运行时加载该class,生成实例对象(instance object),调用method,或对field赋值。这种类似于“看透”了class的特性被称为反射(Reflection),我们可以将反射直接理解为:可以看到自己在水中的倒影,这种操作与直接操作源代码效果相同,但灵活性高得多。

    关于Java的反射API,没必要去记忆,可以在任何JDK API中查询即可:

    Class类:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/Class.html

    reflect包:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/reflect/package-summary.html

    反射的实现方式

    复制代码
    package myblog.rtti;
    
    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    
    /**
     * @project MyBlog
     * @create 2013年6月28日 下午3:00:33
     * @version 1.0.0
     * @author 张广
     */
    public class ToyReflection {
            public static void printInfo(String info, Object obj) {
                    if (obj.getClass().isArray()) {
                            System.out.println(info + ": ");
                            int length = Array.getLength(obj);
                            System.out.println("Array Size: " + length);
                            for (int i = 0; i < length; i++) {
                                    System.out.print("Array[" + i + "]: " + Array.get(obj, i) + ", ");
                            }
                            if (length != 0)
                                    System.out.println();
                    }
                    System.out.println(info + ": " + obj.toString());
            }
    
            public static void main(String[] args) {
    
                    try {
                            // 获得类对象
                            Class<?> c = Class.forName("myblog.rtti.Toy");
                            printInfo("获得类对象", c);
    
                            // 获得超类
                            Class<?> superClass = c.getSuperclass();
                            printInfo("获得超类", superClass);
    
                            // 获得所有父接口
                            Class<?>[] interfaces = c.getInterfaces();
                            printInfo("获得所有父接口", interfaces);
    
                            // 实例化
                            Toy toy = (Toy) c.newInstance();
                            printInfo("实例化", toy);
    
                            // 获得访问属性为public的构造方法
                            Constructor<?>[] constructors = c.getConstructors();
                            printInfo("获得构造方法", constructors);
    
                            // 获得指定参数的构造方法
                            Constructor<?> constructor = c.getDeclaredConstructor(String.class, String.class, int.class);
                            printInfo("获得指定构造方法", constructor);
    
                            // 获得方法,getMethod只能获得public方法,包括父类和接口继承的方法
                            Method method = c.getMethod("playToy", String.class);
                            printInfo("获得公有方法", method);
    
                            // 调用方法
                            method.invoke(toy, "张三");
    
                            // 获得修饰符,包括private/public/protect,static
                            String modifier = Modifier.toString(method.getModifiers());
                            printInfo("获得修饰符", modifier);
    
                            // 获得参数类型
                            Class<?>[] paramTypes = method.getParameterTypes();
                            printInfo("获得参数类型", paramTypes);
    
                            // 获得返回值类型
                            Class<?> returnType = method.getReturnType();
                            printInfo("获得返回值类型", returnType);
    
                            // 获得异常类型
                            Class<?>[] excepTypes = method.getExceptionTypes();
                            printInfo("获得异常类型", excepTypes);
    
                            // 调用私有方法,getDeclaredMethod获得类自身的方法,包括public,protect,private方法
                            Method method2 = c.getDeclaredMethod("buildMsg", String.class);
                            method2.setAccessible(true);
                            String result = (String) method2.invoke(toy, "李四");
                            printInfo("获得私有方法", result);
    
                            // 获得全部属性
                            Field[] fields = c.getFields();
                            printInfo("获得全部属性", fields);
    
                            // 获得类自身定义的指定属性
                            Field field = c.getDeclaredField("name");
                            printInfo("获得自身属性", field);
    
                            // 获得类及其父类,父接口定义的public属性
                            Field field2 = c.getField("color");
                            printInfo("获得公有属性", field2);
    
                            // 获得权限修饰符,包括private/public/protect,static,final
                            String fieldModifier = Modifier.toString(field.getModifiers());
                            printInfo("获得权限修饰符", fieldModifier);
    
                            // 操作数组
                            int[] exampleArray = { 1, 2, 3, 4, 5 };
                            // 获得数组类型
                            Class<?> componentType = exampleArray.getClass().getComponentType();
                            printInfo("数组类型", componentType.getName());
                            // 获得长度
                            printInfo("数组长度", Array.getLength(exampleArray));
                            // 获得指定元素
                            printInfo("获得数组元素", Array.get(exampleArray, 2));
                            // 修改指定元素
                            Array.set(exampleArray, 2, 6);
                            printInfo("修改数组元素", exampleArray);
    
                            // 获得当前的类加载器
                            printInfo("获得当前类加载器", toy.getClass().getClassLoader().getClass().getName());
                    } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                    } catch (InstantiationException e) {
                            e.printStackTrace();
                    } catch (IllegalAccessException e) {
                            e.printStackTrace();
                    } catch (SecurityException e) {
                            e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                    } catch (IllegalArgumentException e) {
                            e.printStackTrace();
                    } catch (InvocationTargetException e) {
                            e.printStackTrace();
                    } catch (NoSuchFieldException e) {
                            e.printStackTrace();
                    }
    
            }
    }
    复制代码

     通过上面的代码,可以清晰的理解如何“在水中看到自己”,不过需要注意的有几点:

    1,在java的反射机制中,getDeclaredMethod得到的是全部方法,getMethod得到的是公有方法;

    2,反射机制的setAccessible可能会破坏封装性,可以任意访问私有方法和私有变量;

    3,setAccessible并不是将private改为public,事实上,public方法的accessible属性也是false的,setAccessible只是取消了安全访问控制检查,所以通过设置setAccessible,可以跳过访问控制检查,执行的效率也比较高。参考:http://blog.csdn.net/devilkin64/article/details/7766792

    反射的性能

    反射机制给予Java开发很大的灵活性,但反射机制本身也有缺点,代表性的缺陷就是反射的性能,一般来说,通过反射调用方法的效率比直接调用的效率要至少慢一倍以上。

    关于性能的问题,可以参考这篇博客http://blog.csdn.net/l_serein/article/details/6219897

    反射与设计模式

    反射的一个很重要的作用,就是在设计模式中的应用,包括在工厂模式和代理模式中的应用。关于这一方面,我会在后续的文章中介绍,有兴趣的朋友也先可以参考这篇文章http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html中关于动态代理模式实现方法的文章。

    参考资料

    JAVA编程思想,第14章

    Java-RTTI与反射机制--详细 :http://blog.csdn.net/dahaizisheng/article/details/1762327

    Java反射详解:http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html

    RTTI和反射机制:http://blog.sina.com.cn/s/blog_5ea2d6840100v9bu.html

    Java中的RTTI和反射机制:http://blog.csdn.net/a81895898/article/details/8457623

    Java反射性能测试:http://blog.csdn.net/l_serein/article/details/6219897

  • 相关阅读:
    HBase 高性能加入数据
    Please do not register multiple Pages in undefined.js 小程序报错的几种解决方案
    小程序跳转时传多个参数及获取
    vue项目 调用百度地图 BMap is not defined
    vue生命周期小笔记
    解决小程序背景图片在真机上不能查看的问题
    vue项目 菜单侧边栏随着右侧内容盒子的高度实时变化
    vue项目 一行js代码搞定点击图片放大缩小
    微信小程序进行地图导航使用地图功能
    小程序报错Do not have xx handler in current page的解决方法
  • 原文地址:https://www.cnblogs.com/hushaojun/p/4948546.html
Copyright © 2011-2022 走看看