java编程思想读书笔记----第十四章 类型信息

来源:互联网 发布:阿里云学生机有啥用 编辑:程序博客网 时间:2024/04/28 06:09

  运行时类型信息可以使你在程序运行时发现和使用类型信息。java有两种方式让我们在运行时识别对象和类的信息,一种是传统的RTTI,一种是“反射”机制,它允许我们在运行时发现和使用类的信息。

1、为什么需要RTTI

  当存在一个基类Shape ,它有三个子类A、B、C,将子类放入List<Shape>中时会向上转型为Shape,实际上,list中保存的是Object,从list中取出时会转换成Shape,丢失了其具体类型。可以确定的是,list中保存的都是Shape,这在编译时通过泛型强制确认这一点;而在运行时,由类型转换操作保证这一点。接下来,由多态机制决定实际执行什么样的代码。通常,也是这样要求的,大部分代码尽可能的少了解对象的具体类型,而只与通用类型打交道。这样代码易写易读易维护;设计也更容易实现和理解。但是,如果碰到某种特殊的编程问题,比如,旋转所有Shape,但是需要跳过B类,此时,如果知道该泛化引用的确切类型,就可以简单的解决。可以通过RTTI获取确切类型。

2、Class对象

  java使用Class对象来执行其RTTI。每一个类都有一个Class对象,每当编写并且编译了一个类,就会产生一个Class对象(包含在.class文件中)。JVM使用类加载器生成这个类的对象。所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。因此,构造器也是类的静态方法,即使没有static关键字。所以,java程序在开始运行之前并没有被完全加载。类加载器首先会检查该类的Class对象是否被加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件。
  getSimpleName()和getCanonicalName()分别产生不含包名的类名和全限定的类名。
2.1 类字面常量
  除了Class.forName(),还可以使用类字面常量来生成对Class对象的引用,即A.class;这样更简单,安全(编译期检查),高效。类字面常量不仅可以应用于普通的类,还可以应用于接口、数组以及基本数据类型。
  使用类前的准备工作包含三个步骤:

  1. 加载,由类加载器执行。该步骤将查找字节码,并从这些字节码中创建一个Class对象。
  2. 链接。验证类中的字节码,为静态域创建存储空间,并且如果有必要的话,将解析这个类创建的对其它类的引用。
  3. 初始化。如果该类具有超类,则对其初始化。
      不同于forName(),当使用“.class”创建对Class对象的引用的时候,不会自动的初始化该Class对象。初始化被延迟到对静态方法或者非常数静态域进行首次引用才执行。
      如果一个static final值是“编译期常量”,那么这个值不需要对类进行初始化就可以被读取。但是,如果只是将一个域设置为static和final还不足以确保这种行为,因为它可能不是一个编译期常量。如果一个static域不是final的,那么在对它访问时,总是要求在读取前进行初始化和链接。
    2.2、泛化的class 引用
      为了提供编译期检查,向Class引用添加了泛型语法。 使用newInstance()要求该类型有一个默认构造函数,返回的是一个Object类型。

3、转换前先做类型检查

  当前已知的RTTI形式:1)、传统的类型转换。如“(Shape)”,执行一个错误的转换会抛出异常。2)、代表对象类型的Class对象。3)关键字 instanceof。
  在传统的类型转换中,java要执行类型检查(类型安全的向下转型)。

x instanceof A

  检查对象x是否从属于A类。对instanceof比较有着严格的限制:只能将其与命名类型比较,而不能与Class对象比较。 代码中不应该出现过多的instanceof。
3.1、使用类字面常量
3.2、动态的instanceof
  class.isInstanceof()提供了一种动态测试对象类型的方法。

4、注册工厂

  

5、instanceof和Class的等效性

  instanceof和isInstance()的结果完全一样,==和equals()的结果也是一样的。instanceof保持了类型的概念,它比较的是对象是否是这个类或这个类的派生类,而==比较的是Class对象,没有考虑继承——它或者是这个确切的类型,或者不是。

6、反射:运行时的类型信息

  如果不知道某个对象的确切类型,RTTI可以告诉你。但是又一个限制:这个类型在编译时可知,这样才能使用RTTI识别它。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。
  Class类和java.lang.reflect类一起对反射的概念进行了支持,该类库包含了Field、Method和Construct类(每个类都实现了Member接口)。用get()和set()方法读取和修改与Field对象关联的字段,用invoke()调用与Method对象关联的方法。
  RTTI的真正区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

7、动态代理

  java的动态代理可以动态的创建代理并动态的处理对所代理方法的调用。在动态代理上做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的策略。
  通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要一个类加载器(可以从已被加载的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或者抽象类),以及InvocationHandler接口的一个实现。动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。

8、空对象

  

9、接口与类型信息

  interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。但是通过类型信息,这种耦合性还是会传播出去。
  假设有一个接口A,B类是A的实现,当我们创建一个B的对象b向上转型为A的对象a时,通过RTTI发现a是由b实现的,可以将a转型成B类,从而调用不再A接口中的B类的方法。可以通过对实现使用包访问权限解决这种问题。但是,通过反射仍可以到达并调用所有实现类中的方法(不包含在接口中),无论是将实现以内部类或匿名类的方式完成,都无法避免。
  一方面,当代码中使用了这些访问权限,则意味着使用者不应该去调用它们;另一方面,总是在类中留下后门,可以使你解决某些特定的问题,通过反射带来的好处是不言而喻的。

0 0
原创粉丝点击