java编程思想读书笔记 第十四章 类型信息(下)

发布于:2021-10-19 09:09:37

1. 反射:运行时类的信息
Class类与jsvs.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Filed对象管理的字段,用invoke()方法调用与Method对象关联的方法。还可以调用getFields()、getMethods()和getConstructors()等方法,以返回表示字段、方法以及构造器的对象的数组。Class.forName()生成的结果在编译时是不可知的,因此所有方法的特征签名信息都是在执行时被提取出来的。
反射的作用:反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法。反射在java中是用来支持其他特性的,例如对象序列化和JavaBean。
2. 动态代理
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种。
1)静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
2)动态代理:在程序运行时,运用反射机制动态创建而成。动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。
JDK动态代理中包含一个类和一个接口:


InvocationHandler接口:
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}

参数说明:
Object proxy:指被代理的对象。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数
可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。


3. 空对象
对象为空,除了表示为Null以外,还可以使用空对象,所谓空对象,它占了个位置,但是却并不返回实际靠谱的数据,是null更智能化人性化的替代物。空对象与null相比最有用之处在于它更接*数据。可以用instanceof来探测对象只是Null还还是NullObject,单例模式下(通常,空对象都是单例),还可以用equals()或者==来比较是否为NullObject。
空对象可以很方便的用于“虚位以待”的情况,比如即使在没有这个实际对象的情况下,想要输出“We don’t have this person”而不是“null”时,可以使用空对象,当然要自己构建空对象类.


4. 接口与类型信息
interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。为了避免客户端程序员将接口类对象向下转型,可以对实现使用包访问权限。对Method对象或者域对象调用setAccessible(true)方法,可以获取各种甚至private权限的对象。例子如下:
包访问权限的类C:


public interface A {
void f();
}
class C implements A {
public void f() {
System.out.println("public C.f()");
}
public void g() {
System.out.println("public C.g()");
}
void u() {
System.out.println("package C.u()");
}
protected void v() {
System.out.println("protected C.v()");
}
private void w() {
System.out.println("private C.w()");
}
}

获取类C的方法:


public class HiddenC {
public static A makeA() {
return new C();
}
}
class HiddenImplementation {
public static void main(String[] args) throws Exception {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getName());
callHiddenMethod(a, "g");
callHiddenMethod(a, "u");
callHiddenMethod(a, "v");
callHiddenMethod(a, "w");
}
static void callHiddenMethod(Object a, String methodName) throws Exception {
Method g = a.getClass().getDeclaredMethod(methodName);
g.setAccessible(true);
g.invoke(a);
}
}
/* Output:
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()

从该例子可看出,通过使用反射,仍旧可以到达并调用所有方法,甚至是private方法。如果知道方法名,你就可以在其Method对象上调用setAccessible(true),就像在callHiddenMethod()中看到的那样。也从中可以看出,对反射没有隐藏任何东西。而对final成员的修改是安全的,但是其实运行时系统会在不抛出异常的情况下不接受任何修改,所以实际上还是没改成。


5. 总结
面向对象程序语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必要的时候使用RTTI。RTTI有时能解决效率问题,如果某个对象应用在多态中非常缺乏效率,那么可以单独挑出来,使用RTTI,为其编写一段特别的代码提高效率。由于反射允许更加动态的编程风格,因此它开创了编程的新世界。

相关推荐

最新更新

猜你喜欢