类型信息

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

1.为什么需要RTTI(运行时类型信息)

在这里插入图片描述
这是一个类层次的结构图,基类位于顶部,派生类向下扩展。面向对象的基本目的是让代码只操纵对基类的引用,这样如果要添加一个新的类来扩展程序,就不会影响到原来的代码。该例子中的shape接口动态绑定draw(),目的是让客户端程序员使用繁华的shape引用来调用draw()。draw在所有的派生类都会被覆盖,并且由于他是被动态绑定的,所以即使是通过繁华的shape引用来调用,也产生正确的行为。
在这里插入图片描述
在这里插入图片描述
基类中的draw方法,通过传递this参数给System.out.println(),间接使用toString()打印类标识符。当某个对象出现在字符串表达式中,toString方法会被自动调用,来生成表示该对象的String。每个派生类都要覆盖toString()方法,这样draw()在不同情况就会打印不同的消息。当把shape对象放入List<Shape>的数组时会向上转型。在向上转型的过程中也丢失了shape对象的具体类型,对于数组而言只是shape类的对象。当从数组中取出元素时,这种容器–经所有的事务都当做Object持有–会自动将结果转型回shape。在java中所有类型的转换都是在运行时进行正确性检查的。该例子中Object被转型为shape而不是Circle、square或triangle。

2.Class对象

class对象就是用来创建类的所偶的“常规”对象的。java使用class对象执行RTTI。类是程序的一部分,每个类都有一个class对象。为生成这个类的对象,运行这个程序的java虚拟机将使用被称为“类加载器”的子系统。类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器。元类加载器加载得是可信类。
所有的类都是在对其第一次使用时动态加载到JVM中,当程序创建第一个对类的静态成员的引用时,就会加载这个类。因此,java程序在他开始裕兴之前并非被完全加载,其各个部分是在必须是才加载的。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,会接受验证,以确保其没有被破坏。一旦某个类的Class对象被加载到内存中,它就被用来创建这个类的所有对象。
在这里插入图片描述
在这里插入图片描述
从输出中看到,Class对象尽在需要的时候才被加载,static初始化是在类加载时进行的。Class.forName(“Gum”),该方法是Class类的static成员,Class对象和其他对象一样,可以获取并操作它的引用。forName()是取得Class对象引用的一种方法,用一个包含目标类的文本名的String作为输入参数,返回一个Class对象的引用。
无论何时,只要想在运行时使用类型信息,必须首先获得对恰当的Class对象的引用。Class.forName就可以实现。该方法不需要为了获得Class引用而持有该类型的对象。
Class的newInstance()方法实现“虚拟构造器”,虚拟构造器允许声明“我不知道你的确切类型,但是无论如何要正确的创建自己”使用newInstance()创建的类必须带有默认构造器。

2.1 类字面常量

java提供另一个方法生成对Class对象的引用,如FancyToy.class;这种在编译时会受到检查,根除对forName方法的调用,所以高效和安全。
类字面常量可以用于类、接口、数组、基本数据类型。对于基本数据类型的包装器类还有一个标准字段TYPE,TYPE字段是一个引用,指向对应基本数据类型的Class对象。
在这里插入图片描述
为了使用类做的准备工作:
1.加载,2.链接,3.初始化。
初始化被延迟到对静态方法或者非常数静态域进行首次引用时才执行。
初始化有效的时间尽可能的“惰性”,从对initable引用的创建中可以看到,仅使用.class获得对类的引用不会引发初始化。但是为了产生Class引用,Class.forName()立即就进行了初始化。如果一个static域不是final的,那么在对他的访问时,总是要求在它被读取之前,进行链接。

2.2 泛化的Class引用

Class引用总是指向某个Class对象,可以制作类的实例,并包含可做用于这些事例的所有方法代码。还包含该类的静态成员,因此Class引用表示就是他所指向的对象的确切的类型。
为了在使用泛化的Class引用时放松限制,使用通配符。Class<?>的好处在于表示并非碰巧或者由于疏忽而使用了一个非具体的类引用。向Class引用添加泛型语法的原因是为了提供编译期类型检查。
下面的例子使用泛型类语法。
在这里插入图片描述
当将泛型语法用于Class对象会发生newInstance将返回该对象的确切类型,不仅只是在ToyTest.java中的基本Object。

2.3 新的转型语法

在这里插入图片描述
cast方法接收参数对象,并且将其转型为Class引用的类型。

3. 类型转换前先做检查

1.传统的类型检查,如shape,有RTTI确保类型转化的正确性,如果执行一个错误类型转化,抛出一个ClassCastException异常。
2.代表对象的类型的Class对象。
RTTI在java中还有第三种形式,是关键字instanceof。返回一个布尔值,告诉对象是不是某个特定类型的实例。例如:
在这里插入图片描述
再将x转型为Dog前,if语句检查对象x是否从属于Dog类型。一般,可能想呀查找某种类型,这时可以轻松的使用instanceof技术所有对象。对于instanceof有比较严格的限制:只能将其与明明类型进行比较,不能与Class对象进行比较。

3.1 使用类字面常量

在这里插入图片描述
在这里插入图片描述

3.2 动态的instanceof

Class.isInstance提供一种动态测试对象的途径。于是所有单调的instanceof语句都可以从PetCount.java的例子中移除。例如:
在这里插入图片描述
为了对所有的不同类型的Pet进行计数,PetCounterMap预加载LiteralCreator.allTypes中的类型。这使用了net.mindview.util.MapData类,这个类接收一个Iteralbe和一个常数值,然后用allTypes中的元素作为键,用0作为值填充Map。

3.3 递归计数

在这里插入图片描述
count方法获取其参数的Class,然后使用isAssignableFrom执行运行时的检查,来校验传递的对象确实属于继承结构。countClass首先对该类的确切类型计数,然后如果其超类可以复制给baseType,countClass将其超类上递归计数。

5. instanceof与Class的等价性

在查询类型信息时,以instanceof的形式与直接比较Class对象的差别如下:
在这里插入图片描述
在这里插入图片描述
test方法使用两种形式的instanceof作为参数来执行类型检查,然后获取Class引用,并用==和equals检查Class对象是否相等。instanceof和isInstance生成的结果完全一样,equals和==也一样。

6. 反射:运行时的类信息

在运行时获取类的信息的一个动机是希望提供在跨网络的远程平台上创建和运行对象的能力。被称为远程方法调用(RMI),允许一个java程序将对象分布在多台机器上。需要这种分布能力的原因有很多,例如:在执行一个需要进行大量计算的任务,为了提高运行速度,想将计算划分为许多小的计算单元,分布到空闲的机器上运行。
Class类与java.lang.reflect类一起对反射的概念进行支持,该类库包含Field、Method、Consturctor类。这些类型的对象由JVM在运行时创建用来表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get()和set()读取和修改与Field对象关联的字段,用invoke方法调用与Method对象关联的方法。还可以调用getFields、getMethods和getConstructors等很便利的方法。
当通过反射与一个未知类型的对象打交道,JVM值简单检查这个对象,看属于那个特定的类。再用它做其他事情之前必须先加载那个类的Class对象。RTTI与反射间的真正区别是,对RTTI来说,编译器在编译时打开和检查.class文件。对反射来说,.class文件在编译时不可获取,所以在运行时打开和检查.class文件。

6.1 类方法提取器

反射在java中用来支持其它特性,例如对象序列化和javabean。Class.forName()生成的结果在编译时是不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的。

7. 动态代理

代理是基本的设计模式之一,为了提供额外的或不同的操作,而插入用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。如下例所示:
在这里插入图片描述
在这里插入图片描述
在任何时候,只要想要将额外的操作从“实际”对象中分离到不同地方,特别是当希望能够容易做出修改,从没有使用额外操作转为使用这些操作,代理很有用。
java动态代理可以动态创建代理并到动态处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理上。

8 空对象

当使用内置的null表示缺少对象时,在每次使用引用时都必须测试其是否是null,势必产生相当乏味的代码。问题在于null除了在试图用它执行任何操作来产生NullPointerException之外,自己没有其他的任何行为。有时引入空对象,它可以接收传递给他的所代表的对象的消息,但是将返回表示为并不存在任何“真实”对象的值。
空对象最有用的在于它更接近数据,因为对象表示的是问题空间的实体。即使空对象可以响应“实际”对象可以响应的所有消息,仍需要某种方式测试是否为空。
在这里插入图片描述
通常,空对象都是单例,因此这里将其作为静态final实例创建。因为Person是不可变的,只能在构造器中设置它的值,然后兑取这些值,但是不能修改。如果想要修改一个NullPerson,只能用一个新的Person对象替换他。

8.1 模拟对象与桩

空对象的逻辑变体是模拟对象和桩。与空对象一样,他们都表示在最终的程序中所使用的“实际”对象。但是,模拟对象和桩都只是假扮可以传递实际信息的存活对象,而不是像空对象那样可以成为null的一种更加智能化的代替物。
模拟对象和桩之间的差异在于,模拟对象只是轻量级和自测试的,通常很多模拟对象被创建出来是为了处理各种不同的测试情况。桩只是返回桩数据,通常是重量级的,并且经常在测试之间被复用。装可以根据他们被调用的方式,通过配置进行修改,因此桩是一种复杂对象。对于模拟对象,如果需要做很多事情,通常会创建大量小而简单的模拟对象。

9. 接口与类型信息

interface关键字的目标是允许程序员隔离构建,进而降低耦合性。但是通过类型信息,这种耦合性还会传播出去。如下面例子:
在这里插入图片描述
在这里插入图片描述
通过使用RTTI,发现a被当做B实现,通过将其转型为B,可以调用不在A中的方法。

10.总结

RTTI允许通过匿名基类的引用发现类型信息。
然而使用多态机制的方法调用,要求拥有基类定义的控制权,因为在扩展程序的时候,会发现积累并未包含想要的方法,如果基类来自别人的类或者有别人控制,这是RTTI可解决,继承一个新类然后添加你需要的方法。在代码的其他地方,可以检查你自己特定的类型,并调用你自己的方法。这样不会破坏多态性以及程序的扩展能力,这样添加一个新的类并不需要在程序中搜索switch语句。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值