共计 5654 个字符,预计需要花费 15 分钟才能阅读完成。
本文局部摘自 On Java 8
RTTI
RTTI(RunTime Type Information)运行时类型信息,可能在程序运行时发现和应用类型信息,把咱们从只能在编译期通晓类型信息并操作的局限中解脱进去
传统的多态机制正是 RTTI 的根本应用:假如有一个基类 Shape 和它的三个子类 Circle、Square、Triangle,当初要把 Circle、Square、Triangle 对象放入 List\<Shape\> 中,在运行时,先把放入其中的所有对象都当作 Object 对象来解决,再主动将类型转换为 Shape。所有类型转换的正确性查看都是在运行时进行的,这也正是 RTTI 的含意所在:在运行时,辨认一个对象的类型
但这样的类型转换并不彻底,Object 只是被转型为 Shape,而不是更具体的 Circle、Square、Triangle,如果咱们心愿失去更具体的类型呢?比如说咱们当初须要旋转所有图形,然而想跳过圆形(圆形旋转没有意义),这时能够应用 RTTI 查问某个 Shape 援用所指向对象的确切类型,而后抉择进行适合的解决
Class 对象
家喻户晓,每当咱们编写并编译了一个新类,就会产生一个 Class 对象,它蕴含了与类无关的信息。咱们能够应用 Class 对象来实现 RTTI,一旦某个类的 Class 对象被载入内存,它就能够用来创立这个类的所有对象
Class 对象都属于 Class 类型,既然它也是对象,那咱们就能够获取和操控它的援用。forName() 是 Class 类的一个静态方法,咱们能够应用 forName() 依据指标类的全限定名(蕴含包名)失去该类的 Class 对象。应用 forName() 会有一个副作用,那就是如果这个类没有被加载就会加载它,而在加载的过程中,Gum 类的 static 初始块会被执行。当 Class.forName() 找不到要加载的类,就会抛出异样 ClassNotFoundException
Class gumClass = Class.forName("Gum");
应用 Class.forName() 你不须要先持有这个类型的对象,但如果你曾经领有了指标类的对象,那就能够通过调用 getClass() 办法来获取 Class 援用,这个办法来自根类 Object,它将返回示意该对象理论类型的 Class 对象的援用
Gum gum = new Gum();
Class gumClass = gum.getClass();
另外,你还能够调用 getSuperclass() 办法来失去父类的 class 对象,再用父类的 Class 对象调用该办法,反复屡次,你就能够失去一个残缺的类继承构造
Class 对象的 newInstance() 办法能够让你在不晓得一个的确切类型的时候创立这个类的对象,应用 newInstance() 来创立的类,必须带有无参数的结构器
Object obj = gumClass.newInstance();
当然,因为失去的是 Object 的援用,目前你只能给它发送 Object 对象能承受的调用。如果你想申请具体对象才有的调用,你就得先获取该对象的更多类型信息,并执行转型
Java 还提供了另一种生成类对象的援用:类字面常量,这样做不仅更简略,而且更平安,因为它在编译时就会收到查看(不必放在 try 语句块中),而且铲除了对 forName() 办法的调用,效率更高
Class gumClass = Gum.class;
类字面常量不仅能够用于一般类,也能够用于接口、数组以及根本数据类型。对于根本数据类型的包装类,还有一个规范字段 Type,Type 字段是一个援用,指向对应根本数据类型的 Class 对象,例如 int.class 就等价于 Integer.TYPE。还有一点值得注意的是:应用 .class 语法来取得对类对象的援用不会触发初始化
到这里咱们都晓得了,Class 援用总是指向某个 Class 对象,而 Class 对象能够用于产生类的实例。不过自从 Java 引入泛型当前,咱们就能够应用泛型对 Class 援用所指向的 Class 对象的类型进行限定,让它的类型变得更具体些
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
intClass = genericIntClass; // 同一个货色
// genericIntClass = double.class 非法
好了,既然拿到了 Class 对象,那咱们就能够这个类的类型信息,罕用的办法如下:
办法 | 用处 |
---|---|
asSubclass(Class<U> clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 取得类的加载器 |
getClasses() | 返回一个数组,数组中蕴含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中蕴含该类中所有类和接口类的对象 |
forName(String className) | 依据类名返回类的对象 |
getName() | 取得类的残缺门路名字 |
newInstance() | 创立类的实例 |
getPackage() | 取得类的包 |
getSimpleName() | 取得类的名字 |
getSuperclass() | 取得以后类继承的父类的名字 |
getInterfaces() | 取得以后类实现的类或是接口 |
类型转换检测
到目前为止,咱们已知的 RTTI 类型包含:
- 传统的类型转换,如多态
- 代表对象类型的 Class 对象
RTTI 在 Java 中还有第三种模式,那就是关键字 instanceof,它返回一个布尔值,通知咱们对象是不是某个特定类型的实例,能够用发问的形式应用它
if(x instanceof Dog) {((Dog)x).bark();}
Java 还提供了 Class.isInstance() 办法动静检测对象类型,例如
0 instance of String // 编译报错
String.class.isInstance(0) // 能够通过编译
反射
如果你不晓得对象的确切类型,RTTI 会通知你,然而有一个限度:必须在编译时晓得类型,能力应用 RTTI 检测它。换句话说,编译器必须晓得你应用的所有类
看上去这并不是什么特地大的限度,但假如你援用了一个不在程序空间中的对象,比方你从磁盘文件或网络连接中取得大量的字节,并被告知这些字节代表一个类,那该怎么办呢?
类 Class 反对反射的概念,java.lang.reflect 库中反对类 Field、Method、Constructor(每一个都实现了 Member 接口),这些类型的对象由 JVM 运行时创立,以示意未知类中的对应成员。通常咱们不会间接应用反射,但反射能够用来反对其余 Java 个性,例如对象序列化等
Field 代表类的成员变量(成员变量也称为类的属性),Class 类中定义了如下办法用来获取 Field 对象
办法 | 用处 |
---|---|
getField(String name) | 取得某个私有的属性对象 |
getFields() | 取得所有私有的属性对象 |
getDeclaredField(String name) | 取得某个属性对象 |
getDeclaredFields() | 取得所有属性对象 |
Field 类定义了如下办法设置成员变量的信息
办法 | 用处 |
---|---|
equals(Object obj) | 属性与 obj 相等则返回 true |
get(Object obj) | 取得 obj 中对应的属性值 |
set(Object obj, Object value) | 设置 obj 中对应属性值 |
Method 代表类的办法,Class 类中定义了如下办法用来获取 Method 对象
办法 | 用处 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 取得该类某个私有的办法 |
getMethods() | 取得该类所有私有的办法 |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 取得该类某个办法 |
getDeclaredMethods() | 取得该类所有办法 |
Method 类定义了如下办法对办法进行调用
办法 | 用处 |
---|---|
invoke(Object obj, Object… args) | 传递 object 对象及参数调用该对象对应的办法 |
Constructor 代表类的结构器,Class 类中定义了如下办法用来获取 Constructor 对象
办法 | 用处 |
---|---|
getConstructor(Class…<?> parameterTypes) | 取得该类中与参数类型匹配的私有构造方法 |
getConstructors() | 取得该类的所有私有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 取得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 取得该类所有构造方法 |
Constructor 代表类的构造方法
办法 | 用处 |
---|---|
newInstance(Object… initargs) | 依据传递的参数创立类的对象 |
除了成员变量、办法和结构器以外,反射还能获取其余更多的信息,例如注解等,具体可查阅 Java API
反射的弱小威力大家曾经看到了,通过反射咱们甚至能够获取到一些“本不应该获取”的信息,例如程序员为了升高耦合,往往会应用接口来隔离组件,但反射却能够轻易破解
public interface A {void f();
}
class B implements A {public void f() {}
public void g() {}
}
public class InterfaceViolation {public static void main(String[] args) {A a = new B();
a.f();
// a.g(); // 编译谬误
if (a instanceof B) {B b = (B) a;
b.g();}
}
}
通过应用 RTTI,咱们发现 a 是用 B 实现的,只有将其转型为 B,咱们就能够调用不在 A 中的办法。如果你不心愿客户端开发者这样做,那该如何解决呢?一种解决方案是间接申明为理论类型,另一种则是让实现类只具备包拜访权限,这样包内部的客户端就看不到实现类了
除了这个以外,通过反射能够取得所有成员信息,包含 private 的,通常这种违反拜访权限的操作并不是十恶不赦的,兴许还能够帮忙你解决某些特定类型的问题
动静代理
代理是根本的设计模式之一,一个对象封装实在对象,代替实在对象提供其余不同的操作,这些操作通常波及到与实在对象的通信,因而代理通常充当两头对象。上面是一个简略的动态代理的示例:
interface Interface {void doSomething();
}
class RealObject implements Interface {
@Override
public void doSomething() {System.out.println("doSomething");
}
}
class SimpleProxy implements Interface {
private Interface proxied;
SimpleProxy(Interface proxied) {this.proxied = proxied;}
@Override
public void doSomething() {System.out.println("SimpleProxy doSomething");
proxied.doSomething();}
}
class SimpleProxyDemo {public static void consumer(Interface iface) {iface.doSomething();
}
public static void main(String[] args) {consumer(new RealObject());
consumer(new SimpleProxy(new RealObject()));
}
}
当你心愿将额定的操作与实在对象做拆散时,代理可能会有所帮忙,而 Java 的动静代理更进一步,不仅动态创建代理对象,而且能够动静地解决对代理办法的调用。在动静代理上进行的所有调用都会重定向到一个 调用处理程序,该程序负责发现调用的内容并决定如何解决,上面是一个简略示例:
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
DynamicProxyHandler(Object proxied) {this.proxied = proxied;}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy {public static void consumer(Interface iface) {iface.doSomething();
}
public static void main(String[] args) {RealObject real = new RealObject();
Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxyHandler(real));
consumer(proxy);
}
}
通过调用静态方法 Proxy.newProxyInstance() 来创立动静代理,该办法须要三个参数:类加载器、心愿代理实现的接口列表、以及接口 InvocationHandler 的一个实现。InvocationHandler 正是咱们所说的调用处理程序,动静代理的所有调用会被重定向到调用处理程序,因而通常为调用处理程序的构造函数提供一个实在对象的援用,以便执行两头操作后能够转发申请