本章将讨论Java是如何让我们在运行时识别对象和类的信息的:
//建立静态代理以扩展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(/*...*/);
}
}
一种是“传统的”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{
}
}
//调用处理器 实现了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域。