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

Posted Chouney

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thinking in Java 整理笔记:类型信息相关的知识,希望对你有一定的参考价值。

本章将讨论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子句被执行( j vm在装载类时会执行类的静态代码段,要记住静态代码是和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域。

以上是关于Thinking in Java 整理笔记:类型信息的主要内容,如果未能解决你的问题,请参考以下文章

Thinking in Java 整理笔记:内部类

Thinking in Java 整理笔记:内部类

Thinking in Java 整理笔记:字符串

Thinking in Java 整理笔记:字符串

Thinking in Java 整理笔记:通过异常处理错误

Thinking in Java 整理笔记:通过异常处理错误