RTTI
运行时类型信息使得你可以在程序运行时发现和使用类型信息。即在运行时识别对象和类的信息:1. 编译时已经知道了所有的类型。 2. 反射。
在java 中所有的类型转换都是在运行时候进行正确性检查的。这也是 RTTI 名字的含义:在运行时识别一个对象的类型。
大部分的代码尽可能地少了解对象的具体类型,而是只与对象家族中的一个通用表示打交道。这样的代码容易写,读,且便于维护;设计也更容易实现,理解和改变。这正是“多态”是面向对象编程的基本目标。
在Java 中最典型的例子就是在非泛型的编程中所有类型判断都是留在运行时再去决定是否正确 。
在运行时表示类型信息的这项工作,由Class 对象完成,它包含了与类有关的信息。每个类都有一个 Class 对象,每当编写并编译了一个新类就会产生一个 Class 对象(被保存在了一个同名的 .class 文件中)。为了生成这个类对象,运行这个程序的 Java 虚拟机( JVM )将使用被称为“类加载器”的子系统。
所有的类都是在对其第一次使用时,动态加载到JVM 中的。当程序创建第一个对类的静态成员引用时,就回家在这个类。 所以构造器也是类的静态方法。
因此Java 程序在它开始运行之前并非被完全加载,其各个部分实在必须时才加载的。使用时类加载器首先会检查这个类的 Class 对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找 .class 文件。
Static初始化时在类加载时进行的。
如果一个static final 值时“编译期常量”,那么这个值不需要对 Initable 类进行初始化就可以被读取。注意:必须是编译期常量才可以确保这种行为。如下代码即不为编译期常量:
Static final int staticFinal = ClassInitialization.rand.nextInt(100);
1. Class对象
① 简单使用方式
forName ( String className) 方法: 返回与带有给定字符串名的类或接口相关联的 Class 对象。 并非是给定类或接口的对象引用。等效于类名 .class。 是一个 Class对象。
newInstance () 方法: 创建此 Class 对象所表示的类的一个新实例。 使用 newInstance()方法创建的类,必须带有默认的构造器。
如:
//打印出的是Class对象,并非Object引用
System. err .println( "Class" + Class . forName ( "com.chuyang.Presentation" ));
System. err .println( "Class2" +Presentation. class );
//Object引用
System. err .println( "Object" + Class . forName ( "com.chuyang.Presentation" ).newInstance());
System. err .println( new Presentation());
② 类字面常量
这是另一种方法来生成对Class 对象的引用,即使用类字面常量——类名 .class 。
优点在于:在编译期间进行检查(不需要至于try-catch 中),更高效。
对于基本数据的包装器类,有一个标准字段TYPE:
Boolean.TYPE 等价于 boolean.class
Character.TYPE 等价于 char.class
.....
③ 泛化的 Class引用
可以使用泛型指定Class 引用所指向的对象的确切类型。
Class<?>优于平凡的 Class ,即使他们是等价的。可以使用 extends 关键字创建一个范围,如:
Class<? extends Number>。
④ 第三种RTTI —— instanceof
对于instanceof 有比较严格的限制:只可以将其与命名类型进行比较,而不能与 Class 对象作比较。与 Class 的 isInstance() 方法是等价的。
2. 反射
如果不知道一个对象的准确类型,RTTI 会帮助我们调查。但却有一个限制:类型必须是在编译期间已知的,否则就不能用 RTTI 调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道 RTTI 要处理的所有类。
在传统的程序设计环境中,出现这种情况的概率或许很小。但当我们转移到一个规模更大的编程世界中,却必须对这个问题加以高度重视。第一个要注意的是基于组件的程序设计。 此种设计实现中,程序员并不知道所要处理的类。 “反射 ” 提供了一种特殊的机制,可以侦测可用的方法,并产生方法名。
Class类与 java.lang.reflect 类库一起对反射的概念进行了支持。
编写方法调用未知类的未知方法:
/**
* 利用反射调用未知类的未知方法
* @param classObj 类的Class对象
* @param methodName 调用的方法名
* @param objs 方法参数
*/
public void TestReflect( Class classObj,String methodName,Object[] objs) {
try {
//1.第一步获取方法的映射
/**
* 目的是获取execute方法的映射,第一个参数是方法,第二个参数是execute方法所需要的参数列表,类型是class
* 所以当execute方法没有参数时,getMethod第二个参数为null即可
*/
Method method = classObj.getMethod(methodName, new Class[] { String[]. class , Integer. class }) ;
//2.第二步调用方法
/**
* 目的是调用实例invokeTest的execute方法,参数(包含一个String数组和一个Integer对象)类型是Object
* invoke()方法的第一个参数表示被调用的方法所属类的实例,所以如果execute是静态方法,
* invoke的第一个参数可以为空
*/
Object[] result = (Object[])method.invoke(classObj.newInstance(),objs);
//打印出返回值
for ( int i=0;i<result. length ;i++){
System. out .print(result[i]);
}
} catch (Exception e) {
e.printStackTrace();
}
}
execute 方法:
public class InvokeTest {
public static void main(String[] args) {}
public String[] execute(String[] str, Integer intN) {
for ( int j = 0; j < str. length ; j++) {
System. out .println(str[j]);
}
System. out .println(intN.intValue());
return new String[]{ "display " , "have " , "bean " , "finished " , " !" };
}
}
3. 动态代理
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式一般涉及到的角色有:
抽象角色:声明真实对象和代理对象的共同接口;
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
动态代理的主要作用就是:实现了日志和业务的分开,也就是某个类只是要提供了某些业务,比如银行取款业务。
这个类实现了取款业务的同时也需要实现日志功能,如果不用动态代理的话,那么由此一来该类代码里面已经额外地添加了自己不该添加的日志功能能代码。所以我们就得使用动态代理把它的业务代码和日志功能代码分开。所以用到了动态代理概念,spring 里面的 AOP 就是一个很好的例子。
实现了业务和日志的分离也带来很好的独立性,你在开发时一般都是需要日志功能的,但是你开发完毕后有的日志你不想打印出来怎么办呢?所以动态代理给了你很好的设计思想。
普通代理图示:
利用这样的思想,可以讲额外的操作从“实际”对象(真实角色)中分离到不同的地方,特别是你希望很容易的做出修改,代理就会显得很有用,比如:在业务开发中使用日志功能,但是日志并不和业务逻辑有关,并且你希望随时打开或关闭此功能,如果在“实际”对象中写入日志代码,后期的打开关闭此功能就会显得很复杂,而使用一个代理类恰好能解决此问题,在代理类中封装日志功能。因为使用此类的方法并不知道程序传入的是真实角色还是代理角色,所以可以通过传入对象的不同(实际类 /代理类 ),控制此功能的打开或关闭。
静态代理的缺点
如果接口添加一个新的方法,所有的实现类和代理类都要做这个实现,这就增加了代码的复杂度,而动态代理就可以解决这个问题。
动态代理
动态代理和静态代理相比较,最大的好处就是接口中声明的所有的方法都被转移到一个集中的方法中去处理,这样在接口中声明的方法比较多的情况下我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
动态代理 只能代理接口 ,代理类都需要实现InvocationHandler 类,实现 invoke 方法。该 invoke 方法就是调用被代理接口的所有方法时需要调用的,该 invoke 方法的返回值是被代理接口的一个实现类。
动态代理图示:
优点是显而易见的 : 如果接口添加一个新的方法, 只需要 实现类做这个 发放的 实现 ,而无需关心封装到 JDK中的代理类。
示例代码:
接口类:
public interface ProInterface {
void doSomething();
void somethingElse(String arg);
}
具体实现类:
public class RealObject implements ProInterface {
public void doSomething() {
System . err .println( "doSomething" );
}
public void somethingElse(String arg) {
System . err .println( "somethingElse" + arg);
}
}
InvocationHandler 接口的实现类:
public class DynamicProxyHandler implements InvocationHandler {
// 为实现类创建引用
private Object proxied ;
public DynamicProxyHandler(Object proxied) {
this . proxied = proxied;
}
//实现了InvocationHandler的方法
public Object invoke (Object proxy,Method method,Object[] args) throws Throwable {
//插入 需要“分离”的 逻辑操作
//利用反射调用
return method.invoke( proxied , args);
}
}
创建动态代理类,并且使用:
public static void main(String[] args) {
RealObject real = new RealObject();
//创建动态代理
//传入接口的类加载器、接口列表、以及实现了InvocationHandler的类
ProInterface proxy =
(ProInterface)Proxy. newProxyInstance (ProInterface. class .getClassLoader(),
new Class[]{ProInterface. class }, new DynamicProxyHandler(real));
// 使用此代理类,传入方法
consumer (proxy);
}