美文网首页
8.类型信息和反射(Thinking in java学习八)

8.类型信息和反射(Thinking in java学习八)

作者: Vinson武 | 来源:发表于2020-04-16 14:00 被阅读0次

    运行时类型信息

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

    在运行时识别对象和类的信息的方式:

    • 传统的RTTI:它假定我们在编译时已经知道了所有的类型;
    • 反射机制,它允许我们在运行时发现和使用类的信息。

    为什么需要RTTI

    使用RTTI,可以查询某个对象引用所指向的对象的确切类型,然后选择或者剔除特例。

    Class对象

    Class对象就是用来创建类的所有常规对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。
    类是程序的一部分,每个类都有一个Class对象。每当编写并且编译一个新类,就会产生一个Class对象。

    生成该类的对象的操作是由Java虚拟机的“类加载器”子系统的程序运行的。
    类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。

    所有的类都是在其第一次使用时,动态加载到JVM中。Java程序在它开始运行之前并非完全加载。各个部分是在必需时才加载。

    无论何时,只要你想在运行时使用类型信息,就必须要先获得对恰当的Class对象的引用
    Class.ForName()就是实现此功能的途径,因为你不用为了获取Class引用而持有该类型的对象。

    Class it = Class.ForName("类的全限定名");
    

    获得该对象的Class对象的引用;

    Dog dog = new Dog();
    Class it = dog.getClass();
    

    也可以获得该对象的Class对象的引用;

    通过Class对象,你可以发现你想要了解的类型的所有信息

    调用Class.getInterfaces()方法返回的是Class对象,表示在Class对象中包含的接口;如果有一个Class对象的话,可以使用getSuperclass()直接询问直接基类。

    Class的newInstance()方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:我不知道你的确切类型,但是无论如何要正确地创建你自己。使用newInstance()来创建的类,必须带有默认的构造器

    类字面常量

    Java提供了另一种方法来生成对Class对象的引用,即使用类字面常量:Dog.class。这样更简单、安全,因为它在编译器就会接受检查。

    注意:使用Dog.class来创建对象时,不会自动地初始化该Class对象。

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

    1. ==加载==。由类加载器执行,查找字节码(通常是在ClassPath所指定的路径中查找),并从字节码中创建一个Class对象。
    2. ==链接==。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所以引用。
    3. ==初始化==。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

    仅仅使用.class是不会执行初始化操作的,但是Class.ForName()立即就开始了初始化

    泛化的Class引用

    Class引用总是指向某个Class对象,它可以制作类的实例,并包含可作用于这些实例的所有方法代码,还包含静态成员。因此,Class引用表示的就是它所指向的对象的确切类型

    public static void main(String[] args){
        Class intClass = int.class;
        intClass = double.class;
        Class<Integer> gIntClass = int.class;
       // gIntClass = double.class; //Illegal
    }
    

    通过使用泛型语法,可以让编译器强制执行额外的类型检查。

    为了使用泛化的Class引用时放松限制,我使用了通配符?。Class<?>优于平凡的Class,好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用。

    为了创建一个Class引用,它被限定为某种类型的任何子类型,你需要将通配符和extends相结合,创建一个范围。

    Class<<? extends Number>
    public static void main(String[] args){
        Class<? extends Number> gIntClass = int.class;
        gIntClass = double.class;
    }
    

    类型转换前先做检查

    RTTI的形式:

    • 传统的类型转换。
    • 代表对象的类型的Class对象。
    • instanceof,它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。
      对instanceof有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象做比较。

    instanceof与Class的等价性

    instanceof和isInstance()生成的结果完全一样,instanceof保持了类型的概念,它指的是这个类以及这个类的派生类
    而如果用==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。

    class Base{}
    class Sub extends Base{}
    
    test(Object x){
        x instanceof Base;
        x instanceof Sub;
        x.getClass() == Base.class;
        x.getClass() == Sub.class;
    }
     public static void main(String[] args){
         test(new Base());
         test(new Sub());
     }
     //output
     true、false、true、false
     true、true、false、true
    

    反射:运行时的类信息

    RTTI的限制:这个类型在编译时必须已知,这样才能RTTI识别它,并利用这些信息做一些有用的事。
    在编译时你的程序根本无法获知这个对象所属的类。想要在运行时获取类的信息的另外一个动机就是希望提供跨网络的远程平台上的创建和运行对象的能力。(远程方法调用)

    Class类和java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。可以使用Constructor创建新的对象,用get()和set()方法读取和修改Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。

    反射与RTTI真正的区别
    对RTTI来说,编译器在编译时打开和检查.class文件,而反射机制则是在.class文件在编译时是不可获取的,所以反射在运行时打开和检查.class文件。

    类方法提取器

    反射在Java中是用来支持其它特性的,例如对象序列化和JavaBean。

    使用

    package wu.test
    public class RefTest{
        public RefTest() {}
        public RefTest(String a) {}
        public static void main(String[] args) {
            Class<?> c = Class.forName("wu.test.RefTest");
            RefTest obj = (RefTest)c.newInstance(); //必须有无参构造函数才可以
            obj.ref1();
            obj.ref2();
            obj.ref3();
                    
        /*  Method[] methods = c.getMethods();
                Constructor[] ctors = c.getConstructors();
                for(Method m : methods) {
                    System.out.println(m.getName()); //打印所有public方法名
                }
                for(Constructor ct : ctors) {
                    System.out.println("构造方法:" + ct.getName());
                } */
        }
       public void ref1() {
            System.out.println("ref1");
        }
        protected void ref2() {
            System.out.println("ref2");
        }
        private void ref3() {
            System.out.println("ref3");
        }
    }
    

    分别输出 ref1\ref2\ref3

    动态代理

    代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来替代实际对象的对象。

    普通代理模式示例

    interface Interface{
            void doSomeThing();
            void doSomeThingElse(String arg);
        }
        class RealObject implements Interface{
            @Override
            public void doSomeThing() {
                System.out.println("doSomeThing");
            }
    
            @Override
            public void doSomeThingElse(String arg) {
                System.out.println("doSomeThingElse");
            }   
        }
        class ProxyObject implements Interface{
            private Interface proxy;
            
            public ProxyObject(Interface proxy) { //传入实际对象
                this.proxy = proxy;
            }
            @Override
            public void doSomeThing() {
                System.out.println("ProxyObject doSomeThing");
                proxy.doSomeThing(); //实际对象方法
            }
    
            @Override
            public void doSomeThingElse(String arg) {
                System.out.println("ProxyObject doSomeThingElse");
                proxy.doSomeThingElse(arg);
            }       
        }
          class SimpleProxyDemo{
             public static void consume(Interface iface) {
                 iface.doSomeThing();
                 iface.doSomeThingElse("other");
             }
              public static void main(Srting[] args) {
                  consume(new RealObject());
                  consume(new ProxyObject(new RealObject())); //将实际对象传入代理对象构造器
              }
         }
    

    Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。

    在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface),另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
    每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用

    用动态代理重写SimpleProxyDemo

    class DynamiProxyHandler implements InvocationHandler{//实现InvocationHandler这个接口
        private Object proxied;
        public DynamiProxyHandler(Object proxied){
            this.proxied = proxied;
        }
        //重写invoke,方法调用都会转发到这里
        @Override
        public Object invoke(Object proxy, Method method, Object[] args){
            System.out.println("invoke method = " + method);
            return method.invoke(proxied, args); //调用实际对象的方法
        } 
    }
    class SimpleProxyDemo{
             public static void consume(Interface iface) {
                 iface.doSomeThing();
                 iface.doSomeThingElse("other");
             }
              public static void main(Srting[] args) {
                 RealObject real = new RealObject();
                 Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoad(), new Class[]{Interface.class},new DynamicProxyHandler(real));
                 consume(proxy);
              }
         }
    

    从上面的demo看成,调用静态方法

    Proxy.newProxyInstance(
    ClassLoader loader, 
    Class<?>[] interfaces, //接口列表
    InvocationHandler)
    

    可以创建动态代理对象。

    空对象

    空对象可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何真实对象的值。

    其有用之处在它更靠近数据,因为对象表示的是问题空间内的实体。

    相关文章

      网友评论

          本文标题:8.类型信息和反射(Thinking in java学习八)

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