博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java类型信息详解
阅读量:4186 次
发布时间:2019-05-26

本文共 8682 字,大约阅读时间需要 28 分钟。

类型信息

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

本章节将讨论Java是如何让我们在运行时识别对象和类的信息的. 主要有两种方式,一种是"传统的" RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型;另一种是"反射机制",它允许我们在运行时发现和使用类的信息.

Class对象

  1. 要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的.这项工作是由称为Class对象的特殊对象完成的.它包含了与类有关的信息. 事实上,Class对象就是用来创建类的所有"常规" 对象的.

  2. 类是程序的一部分,每个类都有一个Class对象.换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中.)

  3. 所有的类都是对其第一次使用时,动态加载到JVM中的. 当程序创建第一个对类的静态成员引用时,就会加载这个类.这个也证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字. 因此,使用new 操作符创建类的新对象也会被当作对类的静态成员的引用.

这也证明了,Java程序在它开始运行之前并非完全被加载,其各个部分是在必需时才加载的. 类加载器首先检查这个类的Class对象是否已经加载.如果尚未加载,默认的类加载器就会根据类名查找.class文件(例如,某个附加类加载器可能会在数据库中查找字节码).在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一). 一旦某个类的Class对象被载入内存,它就会用来创建这个类的所有对象.

具体可以查看对应的Demo

接下来介绍三种方法来获取Class对象的引用:

  1. Class.forName()
  2. Calss.getSuperClass()
  3. Class.class -- 类字面常量

注意:有一点很有趣,当使用".class"来创建对Class对象的引用时,不会自动地初始化该Class对象. 为了使用类而做的准备工作实际包含三个步骤:

  • 加载, 这是类加载器执行的.该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非时必需的),并且从字节码中创建一个Class对象.
  • 链接. 在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用.
  • 初始化. 如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块.
    初始化被延迟到了对静态方法(构造器隐式地是静态的) 或者非常数静态域进行首次引用时才执行

具体可以查看对应的Demo

  1. 初始化有效地实现了尽可能的"惰性".从所写的Demo中可以得出仅使用.class语法来获得对类的引用不会触发初始化.
  2. 如果一个static final 值是"编译期常量",那么这个值不需要对类进行初始化就可以被读取.
  3. 如果一个static域不是final的,那么对它在访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)初始化(初始化存储空间);

泛化的Class引用

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

在Java SE5中,Class<?> 优于平凡的Class,即便它们时等价的,并且平凡的Class如你所见,不会产生编译器警告信息.Class<?>的好处就是它表示你并非时碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本.

类型转换前先做检查

迄今为止,我们已知的RTTI形式包括:

  1. 传统的类型转换,如"Shape",由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常.
  2. 代表对象的类型的Class对象.通过查询Class对象可以获取运行时所需的信息.
  3. RTTI在Java中还有第三种形式,就是关键字instanceof.他返回一个布尔值,告诉我们对象是不是某个特定类型的实例.可以用提问的方式使用它,就像这样:
if(x instanceof Dog)  ((Dog) x).bark();

反射:运行时的类信息

Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了FieldMethod以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法.另外,还可以调用getFields()getMethods()getConstructors()等很便利的方法,以返回表示字段、方法、以及构造器的对象的数组(在JDK文档中,通过查找Class类可以了解更多相关资料)。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。

重要的是,要认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样)。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。所以RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件。(换句话说,我们可以用“普通”方式调用对象的所有方法)。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

类方法提取器

通常你不需要直接使用反射工具,但是它们在你需要创建更加动态的代码时会很有用.反射在Java中是用来支持其他特性的,例如对象序列化和JavaBean. 但是,如果能动态地提取某个类的信息有的时候还是很有用的.

可以查看代码

Class.forName()生成的结果在编译时时不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的.如果研究一下JDK文档中关于反射的部分,就会看到,反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法.

动态代理

代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替"实际"对象的对象.这些操作通常涉及与"实际"对象的通信,因此代理通常充当着中间人的角色.

下面展示一个简单的示例:

package org.ccgogoing.java.typeinfo;/** * 描述: * 动态代理 * * @outhor chong * @create 2018-05-02 22:54 */public class SimpleProxyDemo {    public static void consumer(Interface iface) {        iface.doSomething();        iface.somethingElse("bonobo");    }    public static void main(String[] args) {        consumer( new RealObject());        consumer(new SimpleProxy(new RealObject()));    }}interface Interface {    void doSomething();    void somethingElse(String arg);}class RealObject implements Interface {    @Override    public void doSomething() {        System.out.println("doSomething");    }    @Override    public void somethingElse(String arg) {        System.out.println("somethingElse " + arg);    }}class SimpleProxy implements Interface {    private Interface proxied;    public SimpleProxy (Interface proxied) {        this.proxied =proxied;    }    @Override    public void doSomething() {        System.out.println("SimpleProxy doSomething");        proxied.doSomething();    }    @Override    public void somethingElse(String arg) {        System.out.println("SimpleProxy somethingElse " + arg );        proxied.somethingElse(arg);    }}/* Output:doSomethingsomethingElse bonoboSimpleProxy doSomethingdoSomethingSimpleProxy somethingElse bonobosomethingElse bonobo*///

因为consumer()接受的Interface,所以它无法知道真正获得的到底时RealObject还是SimpleProxy,因为这二者都实现了Interface.但是SimpleProxy已经插入到了客户端和RealObject之间,因此它会执行操作,然后调用RealObject上相同的方法.

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

查看用动态代理重写的SimpleProxyDemo.java; 请 查看

从上述代码示例,可以看出通过调用Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已加载的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或者抽象类),以及InvocationHandler接口的一个实现.动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递给一个"实际"对象的引用,从而使得调用处理器在执行其中任务时,可以将请求转发.

2438927-b35e1ae01c44ebc0.png

动态代理

探究代理对象proxy

最终的代理对象proxy到底长什么样子呢,是一个什么样的对象去实现对被代理对象方法的调用,下面我们写一个测试类,打印出这个proxy类,如下代理所示:

package org.ccgogoing.java.typeinfo;import java.lang.reflect.*;public class ClassDefinitionPrintUtil {    public static void main(String[] args) {        RealObject real = new RealObject();        Interface proxy = (Interface) Proxy.newProxyInstance(                Interface.class.getClassLoader(),                new Class[] { Interface.class }, new DynamicProxyHandler(real));        System.out.println(proxy.getClass().getName());        //打印出proxy类的结构        printClassDefinition(proxy.getClass());    }    public static void printClassDefinition(Class clz) {        StringBuilder clzModifier = new StringBuilder();        int mod = clz.getModifiers() & Modifier.methodModifiers();        if (mod != 0) {            clzModifier.append(Modifier.toString(mod)).append(' ');        }        String superClz = clz.getSuperclass().getName();        if (superClz != null && !superClz.equals("")) {            superClz = "extends " + superClz;        }        Class[] interfaces = clz.getInterfaces();        String inters = "";        for (int i = 0; i < interfaces.length; i++) {            if (i == 0) {                inters += "implements ";            }            inters += interfaces[i].getName();        }        System.out.println(clzModifier + clz.getName() + " " + superClz + " "                + inters);        System.out.println("{");        Field[] fields = clz.getDeclaredFields();        for (int i = 0; i < fields.length; i++) {            System.out.println("\t" + fields[i].toString() + ';');        }        System.out.println();        Constructor[] constructors = clz.getDeclaredConstructors();        for (int i = 0; i < constructors.length; i++) {            System.out.println("\t" + constructors[i].toString() + ';');        }        System.out.println();        Method[] methods = clz.getDeclaredMethods();        for (int i = 0; i < methods.length; i++) {            System.out.println("\t" + methods[i].toString() + ';');        }        System.out.println("}");    }}

输出如下:

org.ccgogoing.java.typeinfo.$Proxy0final org.ccgogoing.java.typeinfo.$Proxy0 extends java.lang.reflect.Proxy implements org.ccgogoing.java.typeinfo.Interface{    private static java.lang.reflect.Method org.ccgogoing.java.typeinfo.$Proxy0.m1;    private static java.lang.reflect.Method org.ccgogoing.java.typeinfo.$Proxy0.m3;    private static java.lang.reflect.Method org.ccgogoing.java.typeinfo.$Proxy0.m2;    private static java.lang.reflect.Method org.ccgogoing.java.typeinfo.$Proxy0.m4;    private static java.lang.reflect.Method org.ccgogoing.java.typeinfo.$Proxy0.m0;    public org.ccgogoing.java.typeinfo.$Proxy0(java.lang.reflect.InvocationHandler);    public final boolean org.ccgogoing.java.typeinfo.$Proxy0.equals(java.lang.Object);    public final java.lang.String org.ccgogoing.java.typeinfo.$Proxy0.toString();    public final int org.ccgogoing.java.typeinfo.$Proxy0.hashCode();    public final void org.ccgogoing.java.typeinfo.$Proxy0.doSomething();    public final void org.ccgogoing.java.typeinfo.$Proxy0.somethingElse(java.lang.String);}

此时,我们就可以看到Proxy对象的类结构,那么很明显,Proxy.newProxyInstance(Interface.class.getClassLoader(),new Class[] { Interface.class }, new DynamicProxyHandler(real))方法会做如下几件事:

  1. 根据传入的第二个参数interfaces动态生成一个类,实现interfaces中的接口,该例中即Interface接口的 somethingElsedoSomething 方法。并且继承了Proxy类,重写了 hashcode,toString,equals等三个方法。具体实现可参看 ProxyGenerator.generateProxyClass(...); 该例中生成了$Proxy0类。

  2. 通过传入的第一个参数classloder将刚生成的类加载到jvm中。即将$Proxy0类load。

  3. 利用第三个参数,调用$Proxy0$Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象,并且用interfaces参数遍历其所有接口的方法,并生成Method对象初始化对象的几个Method成员变量

  4. $Proxy0的实例返回给客户端。

invoke()方法中传递进来了代理对象,以防你需要区分请求的来源,但是在许多情况下,你并不关心这一点.然而,在invoke()内部,在代理上调用方法时需要格外当心,因为对接口的调用将被重定向为对代理的调用.

通常,你会执行被代理的操作,然后使用Method.invoke()将请求转发给被代理对象,并传入必须的参数.这初看起来可能有些受限,就像你只能执行泛化操作一样.但是,你可以通过传递其他的参数,来过滤某些方法调用:

查看具体示例

从示例代码中我们只查看了方法名,但是你还可以查看方法签名的其他方面,甚至可以搜索特定的参数值.

至此,我们已经看到了,由于反射允许更加动态的编程风格,因此它开创了编程的新世界.

本文所有示例代码均可在上面查看,之后我在学习过程中的一些记录也均会在上面发布.

转载地址:http://puioi.baihongyu.com/

你可能感兴趣的文章
买卖股票的最佳时机 II
查看>>
分发饼干
查看>>
最低票价
查看>>
删列造序
查看>>
使括号有效的最少添加
查看>>
令牌放置
查看>>
回溯法思想
查看>>
子集和问题
查看>>
旅行售货员问题
查看>>
区域和检索 - 数组不可变
查看>>
整数分解
查看>>
最长有效括号
查看>>
救生艇
查看>>
Android中自定义圆形图片(一)
查看>>
Android中ViewPager自动加手动轮播
查看>>
Android中Fragment点击切换与添加ViewPager滑动切换
查看>>
Java多线程-阻塞队列BlockingQueue
查看>>
Windows:Apache与Tomcat集群调优
查看>>
Apache+2Tomcat 集群及调优
查看>>
通向架构师的道路(第三天)之apache性能调优
查看>>