14 类型信息

来源:互联网 发布:linux 声卡驱动 编辑:程序博客网 时间:2024/06/05 08:00

14 类型信息

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

本章讨论java如何让我们在运行时识别对象和类的信息。主要有两种方式:一种是传统的“RTTI”,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制

14.2 Class对象

-每个类都由一个Class对象,每编译一个新类,Class对象就被保存在同名的.class文件中。为了生成这个类的对象,JVM将使用被称为“类加载器”的子系统。

所有类都是第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。构造器也是静态方法,尽管并没有使用static关键字。因此,使用new创建新对象也会被当做对类的静态成员的引用。

类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。

一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

    -
getName() //产生全限定名
getSimpleName() //产生不含包名的类名。
isIterface() // 很显然
getIterfaces() //返回这个类实现的的接口的Class对象数组getSuperclass() //查询其直接基类Classn.newInstance()  //用newInstance创建的类必须有默认的构造器

## 14.2.1 类字面常量
类名.class

注意:当使用.class对象创建对Class对象的引用时,不会自动初始化该Class对象。

如果使用ClassforName()立刻就会进行初始化。

为了使用类而做的准备工作实际有三个步骤
1. 加载 这个是有类加载器执行的,该步骤将查找字节码,并从字节码中创建Class对象。
2. 链接 在链接阶段将验证类中字节码,为静态域分配存储空间。如果有必要的话,将解析这个类创建的对其他类的所有引用。
3. 初始化 如果该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化块。

初始化被延迟到了对静态方法或者非常数静态域进行首次访问时才执行。

如果一个static final值是“编译器常量”,那么这个值不需要对类进行初始化就可以被读取。如果一个static域不是final的,那么对它进行访问时,总是要求它在被读取之前,套进行链接(分配存储空间)和初始化(初始化该存储空间)。

14.2.2 泛化的Class 引用

Class<Integer> genericIntClass = int.class;genericIntClass = Integer.class;//same thinggenericIntClass = double.class; //Illegal

使用泛型语法可以让编译器强制进行额外的类型检查,但是

Class<Number> genericNumberClass = int.class;

是非法的,尽管Number是Integer的父类,但Integer Class对象不是Number Class对象的子类。
想要放松限制,可以使用通配符?

Class<?> intClass = int.class;

尽管Class

Class<? extends Number> bounded = int.class;bounded = double.class; //合法bounded = Number.class; //合法

instanceof和Class的等价性

使用.getClass()和.class判断类型是否相等时,.equal()方法和==结果完全相同。

instanceof和isInstance()结果完全相同。

但instanceof和isInstance()保持了类型的概念,子类和派生类被判定为相同,但是==和equal()不考虑继承。

14.6 反射:运行时的类信息

反射和RTTI的区别在于,RTTI在编译时打开和检查.class文件,反射实在运行时打开和检查.class文件。

动态代理

在任何时刻,只要你想将额外的操作从“实际对象”中分离到不同的地方,特别是当你希望能够很容易做出修改,从没有使用额外操作改为使用这些操作,或者反过来,代理就很有用。

java的动态代理比代理的思想更前进一步,它可以动态的创建代理并动态的处理对代理方法的调用。在动态代理上所做的调用都会重定向到单一的调用处理器(一个实现了InvocationHandler接口的类) 上,它的作用是揭示调用的类型并确定相应的对策。

当通过代理对象proxy调用方法时:proxy.doSomething();
都将会被重定向到DynamicProxyHandler类对象(调用处理器)的invoke()函数。
即,若在invoke()函数中使用proxy调用方法会陷入死循环。

14.9 接口和类型信息

public interface A{    void f();}public class B implements A{    public void f(){}    private void g(){}    protected void u(){}    void v(){}}

使用反射,B中的f,g,u,v方法事实上都是可以访问的。

对于public方法,只需要进行类型判断后把A的引用向下转型为B即可访问。

对于private或者protected方法

public static void callHiddenMethod(Object a,String methodName){    Method g = a.getClass().getDeclaredMethod(methodName);    g.setAccessible(true);    g.invoke(a);}A a = new B();callHiddenMethod(a,"g");callHiddenMethod(a,"u");callHiddenMethod(a,"v");

同样的,对private域只要使用Field对象也可以访问到

pubic class D{    private int i = 1;}D d = new D();Field f = d.getClass().getDeclaredField("i");f.setAccessible(true);sys(f.getInt(d));f.setInt(a,100); //修改了i为100

但是final域在遭遇修改时是安全的,运行时系统会接收修改尝试并且不抛异常,但实际上不会发生任何修改。

原创粉丝点击