Thinking in Java 整理笔记:类型信息

来源:互联网 发布:淘宝无需物流怎么设置 编辑:程序博客网 时间:2024/05/29 15:49
本章将讨论Java是如何让我们在运行时识别对象和类的信息的:
一种是“传统的”RTTI,他假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和实用类的信息。
PS:“运行时类型识别”(RTTI).

1.RTTI:类型信息在运行时是如何表示的?这项工作是由成为Class对象的特殊对象完成的,它包含了与类有关的信息。

2.事实上,Class对象就是用来创建类的所有“常规”对象。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。

3.类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机将使用被称为“类加载器”的子系统。
    类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持Web服务器应用,或者在网络中下载类),那么你有一种方式可以连接额外的类加载器。
    所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员引用时,就会加载这个类。(可由此证明构造器也是类的静态方法)。一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

4.Class对象:Class对象就和其他对象一样,我们可以获取并操作它的引用(这也就是类加载器的操作
    Class.forName(String str)是获取对象的引用的一种方法,返回的是Class对象的引用。但对forName()的调用是为了其“副作用”:如果类str还没有被加载则加载它,期间Str的static子句被执行(jvm在装载类时会执行类的静态代码段,要记住静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了,而且以后不会再执行这段静态代码了
)。(str必须是全限包名)
    getClass():如果有一个感兴趣的类型的对象,则可以通过调用getClass()方法来获取对象的实际类型的Class引用。
    getName():生成全限定的类名,也可以分别使用getSimpleName和getCanonicalName来产生不含包名的类名和全限定的类名。
    getInterface():返回的是Class对象,表示在感兴趣的Class对象中所包含的接口
    getSuperclass():查询其基类。
    newInstance():实现”虚拟构造器“的一种途径,用于创建新实例,会得到Object引用,指向的则是实际的对象。使用newInstace()来创建的类,必须带有默认的构造器。需要注意的是new与newInstance区别:
    关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:1、这个 类已经加载;2、这个类已经连接了而完成上面两个步骤的正是Class的静态方法forName()所完成的newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。 这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。

5.为了使用类而准备的工作实际包含三个步骤:
    1,加载,这是由类加载器执行的。该步骤将查找字节码(通常在c;asspath所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个Class对象。(只是Class对象而不是实际类的对象,因此forName方法并不会执行实际类的构造器从而生成对象)
    2.链接:在连接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的引用。
    3.初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。(初始化被延迟到了对静态方法(如构造器)或者非常数静态域进行首次引用时才执行)

6.类字面常量:Java还提供另一种种方法来生成对Class对象的引用。类名.class(好处是在编译时会受到检查,不用置于try语句)

7.泛化的Class引用:
    在Java SE5中将Class引用的类型变得更具体了,通过Class对象的类型限定实现,用到了泛型语法,如:
    Class<Integer> s=int.class;或者Class<Integer> s=Integer.class;
    但这里需要注意的是://!Class<Number> s=Integer.class;不能被编译。因为虽然Integer继承自Number,但是Integer Class对象却不是Number Class对象的子类

    向Class引用添加泛型语法的原因仅仅是为了提供编译期类型的检查。

8.新的转型语法cast()方法:其接受参数对象,并将其转换成Class所引用的实体类型
       class Storm extends Toy
            Toy t = new Storm();
            Class<Storm> s = Storm.class;
            Toy toy = s.cast(t);

9.instanceof ,如:(x instanceof Toy):表示它是不是某个特定类型的实例。
    instanceof有比较严格的限制:只能将其与命名类型进行比较,而不能与Class对象做比较。
    动态的instanceof: Class.isInstance方法提供了一种动态地测试对象的途径。

10.instanceof 与 isInstanceof 以及== 和equals的比较:
    其中instanceof与isInstanceof结果一样,equals和==也完全一样,instanceof与isInstanceof考虑的包括类以及子类。而equals和==并没有考虑继承。

11.反射与RTTI区别:
    RTTI:这个类型在编译时必须已知,编译器在编译时打开和检查.class文件。
    反射:.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件

    Class类与Java.lang.reflect类库一起对反射概念进行了支持,该类库包含了Field、Method以及Constructor类。这些类
型的对象都是由JVM在运行时创建的,用以表示未知类里对应的成员。
    这样可以使用Constructor创建新对象,用get()、set()方法读取和修改与Field对象有关联的字段。另外可以调用getField(),getMethods,getConstructors()等遍历的方法,以返回表示字段
    通过反射与一个未知类型的对象打交道时,JVM只是简单的检查这个对象,看它属于哪个特定的类(像RTTI那样)。在用它做其他事情之前先加在哪个类的Class对象。因此,哪个类的.class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。

12.动态代理:动态代理比代理的思想更向前一步(代理见复用类一节)动态代理商所有调用都被重定向到单一的调用处理器上。见代码:
//所调用的封装类
public interface Base{ public void add(String arg); public void del(int rowid);}
//实现接口功能的类
public class RealObject implements Base{
     public void add(String arg) throws{
     }
     public void del(int rowid) throws{
  }
}
//建立静态代理以扩展RealObject类
 public class Outter implements Base{
   private Base b;
     public Outter(Base b){
        this.b=b;
    }
     public void add(String arg) throws{
        //....
        b.add(/*...*/);
     }
     public void del(int rowid) throws{
        //....
        b.add(/*...*/);
  }
}
//调用处理器 实现了InvocationHandler
public class TransectHandler implements InvocationHandler{
    private Object proxied;
    public TransectHandler(Object proxied){
        this.proxied = proxied;
    }
//invoke()方法将请求转发给被代理对象,并传入必须的参数。
    public Object invoke(Object proxy,Method method,Object args[])
    throws Throwable{
        return method.invoke(proxied, args);
    }
}
//入口函数
public static void main(String args[]){
            //使用newProxyInstance创建动态代理,参数分别是,对应的类加载器,希望代理实现的接口列表(不是类或抽象类),以及一个InvocationHandler接口的实现
            Base = (Base)Proxy.newProxyInstance(
                    Base .class.getClassLoader(),
                    new Class[]{Base.class},
                    new TransectHandler(new RealObject ()));
            o.add("qweq");
}

13.空对象:引入空对象的思想是很有用的,你可以假设所有对象都是有效的,而不用费精力去检查NULL(有时可以用动态代理的方法创建空对象)。
这样需要某种方式去测试其是否为空,最简单的方式是创建一个标记接口,代码用例如下:
public interface NULL {}
public class Person {
    public final String name;
    public final int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    //这里将其作为静态创建,单例模式
    public static final Person NULL = new NullPerson();
    public static class NullPerson extends Person implements NULL{
        public NullPerson() {
            super(null, 0);
        }
    }
}    
//主函数调用
        Person p = new Person("sss", 5);
            if(p.age==5) p=Person.NULL;

14模拟对象和桩:空对象的逻辑变体是模拟对象和桩。具体并没有太多深究。

15.接口与类型信息:代码:
public interface Storm {
    public void a();
}
public class Toy implements Storm{
    public void a(){}
    public void f(){}
}
            Storm a = new Toy();
            a.a();
           //! a.f();
一般情况下,Storm引用无法调用Toy特有的方法,但是其实可以通过使用RTTI(如a.getClass().getName()获取a的实际对象)在进行向下转型时可以做到的。这从理论上基于了客户端程序员更大的权利
最简单的一种解决方式是对实现使用包访问权限
package test.hidden;
class C implements Storm{
    public void a(){System.out.println("a");}
    public void f(){System.out.println("b");}
    void u(){System.out.println("u");}
    private void z(){System.out.println("z");}
}
public class HiddenC {
    public static Storm makeStorm(){
        return new C();
    }
}
主函数:
Storm a = HiddenC.makeStorm();
这样就无法向下转型,即使能通过RTTI获得实际实现类型。因为包外部没有C类型
然而最关键的是:通过使用反射(getMethod,invoke等)却可以依然到达并调用者所有方法,甚至是private方法,反射技术可以到达并调用那些非公共访问权限的方法,对于域来说也是如此(getField),即便是private域。
0 0
原创粉丝点击