第十四章 类型信息
文章目录
- 概述
- 1.为什需要RTTI
- 2.Class对象
- 类字面常量(类.class)
- 泛化的Class引用
- 新的转型语言
- 3.类型转换前先做检查
- RTTI形式包括
- isInstance()和instaceof
- isAssignableFrom()
- 4.注册工厂
- 5.instanceof和Class的等价性
- 6.反射:运行时的类信息
- java.lang.reflect包含:Field、Method、以及Constructor类
- RTTI和反射的区别
- 7.动态代理
- Proxy类就是用来创建一个代理对象的类
- 例题:
- 8.空对象
- 模拟对象与桩
- 9.接口与类型信息
概述
Java在运行时识别类和对象的信息
- RTTI :假定我们在 编译时 已经知道了所有的类型
- 反射 :允许我们在 运行时 发现和使用类的信息
1.为什需要RTTI
- RTTI(run-time type information):在运行时,识别一个对象的类型
- 使用RTTI可以查询某个基类应用所指向的对象的确切类型,然后选择或剔除特例
2.Class对象
- 类型信息在运行是由Class对象完成
- Class对象用来创建类的所有“常规”对象
- 每当编写并编译了一个新类就会产生一个Class对象
- 为了生成这个类的对象,运行JVM将使用“类加载器”的子系统
- 使用new方法创建类的新对象也会被当作类的静态成员引用
- Class对象仅是在必须才加载,static初始化实在类加载时进行的
- Class对象加载
- Class.forName() : 取得Class对象的引用(使用全限定名)
- class.getName() : 产生全限定名
- class.getSimpleName() : 不含 包名 的类名
- class.getCanonicalName() : 全限定的类名
- class.isInterface() : 该class对象是不是接口
- class.getInterfaces() : 该class对象所包含的接口
- class.getSuperclass() : 查询直接基类
- class.newInstance() : 虚拟构造器,只能调用无参的构造函数
类字面常量(类.class)
- (1) 包装器类,标准字段TYPE(例:char.class – Character.TYPE)
(2) 获取Class类引用的方式
- i.Class.forName 引起类加载
- ii.XXXX.class 类字面常量,不引起类加载
(3) 使用类的三个步骤
- i.加载:查找字节码,创建一个Class对象
- ii.链接:分配存储空间
- iii.初始化:先初始化超类,执行静态初始化器和静态初始化块
泛化的Class引用
- (1) 如果需要Class类型变得更具体些,就通过允许Class引用所指向Class对象的类型进行限定实现。这里用了泛型。如:
Class intClass = int.class; - (2) 如果不确定限定类型,则使用Class<?> 优与 Class
- (3) 如果要扩大限定类型范围(提供编译器类型检查),如:Class<? extends Number> numClass = int.class
- (4) 使用泛化Class引用 创建的对象(newInstance()),得到的不再是Object,而是具体的限定类型,如:
Integer intObj = intClass.newInstance()
- (1) 如果需要Class类型变得更具体些,就通过允许Class引用所指向Class对象的类型进行限定实现。这里用了泛型。如:
新的转型语言
- (1) Class.cast() : 使用Class引用做类型转换
如:
Number numObj= new Integer(1);
Class<Integer> intClass = Integer.class;
Integer intObj = intClass.cast(numObj);
- (2) Class.asSubclass() : 将一个类对象转型为更加具体的类型
动态的instanceof
- 使用instaceof判断对象是否属于某个类、子类、接口实现,也可以使用Class引用的isInstance()方法【String.class.isInstance(new Test())】Class.isInstance不会出现instanceof中编译报错的情况。
3.类型转换前先做检查
RTTI形式包括
- (1) 传统的类型转换(向上、向下),RTTI保证类型正确性,错误则抛出ClassCastException
(2) 传统的类型转换(向上、向下),RTTI保证类型正确性,错误则抛出ClassCastException。
- i.Class c = Class.forName(“RTTITest”);
- ii.Object o = c.newInstance();
- (3) 类型检查(instanceof)
boolean result = obj instanceof Class
a、obj 必须是引用类型,基本类型编译报错。
b、obj 为null,返回false,因此使用instanceof时不用先判断不为空。
c、obj 为Class的实例对象,子类对象,或接口实现对象时,返回true。
d、obj的声明类 和 Class 必须有父子关系(存在转换的可能),否则编译报错。如:
"abc" instanceof Integer 编译报错
isInstance()和instaceof
- 使用instaceof判断对象是否属于某个类、子类、接口实现,也可以使用Class引用的isInstance()方法。
String.class.isInstance(new Test())
Class.isInstance不会出现instanceof中编译报错的情况。isAssignableFrom()
- 使用aClass.isAssignableFrom(bClass) 可以判断aClass引用是否为bClass引用的超类
如:
Exception.class.isAssignableFrom(IOException.class) 结果为true
4.注册工厂
5.instanceof和Class的等价性
- “instanceof” :保持了类型的概念。“你是这个类吗,你是这个类的派生类吗?”
- “==” :比较实际的Class对象,等号左侧是不是右侧这个确切的类型
6.反射:运行时的类信息
- RTTI限制:这个类型在编译时必须已知
- 反射机制:用来检查可用的方法,并返回方法名
java.lang.reflect包含:Field、Method、以及Constructor类
- (1) 使用Constructor:创建新对象
- (2) 使用get()和set()方法读取和修改与Field对象关联的字段
- (3) 使用invoke()方法调用与Method对象关联的方法
- (4) 也可以调用getFields()、getMethod()和getConstructors()以返回表示字段、方法及构造器的对象的数组
RTTI和反射的区别
- (1) RTTI : 编译器在 “编译时” 打开和检查.class文件
- (2) 反射 : 编译器在 “运行时” 打开和检查.class文件
7.动态代理
- 它可以动态的创建代理并动态的处理对所有代理的方法的调用
- 在动态代理上所做的所有调用都会被重定向到单一的调用处理器上
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
(1) proxy:代理类代理的真实代理对象
(2) method:我们所要调用某个对象真实的方法的Method对象
(3) args:指代代理对象方法传递的参数Proxy类就是用来创建一个代理对象的类
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
(1) loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
(2) interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口(如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。)
(3) h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。在Spring中的两大核心IOC和AOP中的AOP(面向切面编程)的思想就是动态代理,在代理类的前面和后面加上不同的切面组成面向切面编程。
例题:
package Chapter14.Test22;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/** * @author:YiMing * @version:1.0 */
public class DynamicProxyHandler implements InvocationHandler {
//代理类中的真实对象
private Object proxied;
//构造函数,给我们的真实对象赋值
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在真实的对象执行之前我们可以添加自己的操作
System.out.println("**** proxy: " + proxy.getClass() +
", method: " + method + ", args: " + args);
if (args != null)
for (Object arg : args)
System.out.println(" " + arg);
System.out.println("before invoke。。。");
//------------------------------------------------------
Object invoke = method.invoke(proxied, args);
//------------------------------------------------------
//在真实的对象执行之后我们可以添加自己的操作
System.out.println("after invoke。。。");
return invoke;
}
}
------------------------------------------------------------------------------------------------------------------
package Chapter14.Test22;
import Chapter14.Test21.Interface;
import Chapter14.Test21.RealObject;
import Chapter14.Test21.SimpleProxy;
import java.lang.reflect.Proxy;
/** * @author:YiMing * @version:1.0 */
public class Test22 {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
//要代理的真实对象
RealObject real = new RealObject();
//代理对象的调用处理程序,我们将要代理的真实对象传入代理对象的调用处理的构造函数中,最终代理对象的调用处理程序会调用真实对象的方法
DynamicProxyHandler handler = new DynamicProxyHandler(real);
SimpleProxy sim=new SimpleProxy(real);
consumer(sim);
/** * 通过Proxy类的newProxyInstance方法创建代理对象,我们来看下方法中的参数 * 第一个参数:Interface.getClass().getClassLoader(),使用handler对象的classloader对象来加载我们的代理对象 * 第二个参数:real.getClass().getInterfaces(),这里为代理类提供的接口是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法 * 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上 */
Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{ Interface.class}, new DynamicProxyHandler(sim));
Interface proxy2 = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), real.getClass().getInterfaces(), handler);
consumer(proxy);
System.out.println("---------------------------------");
proxy2.doSomething();
proxy2.somethingElse("Proxy2");
System.out.println("---------------------------------");
}
}
8.空对象
- 空对象可以接受传递给它的所代表的对象的消息,但是返回表示为实际上并不存在任何“真实”对象的值
- 空对象更靠近数据,因为对象表示的时问题空间的实体
- 创建一个名为Null的标识接口,然后针对不同的业务类创建不同的空对象
模拟对象与桩
- (1) 模拟对象:是轻量级的,自检测的
- (2) 桩:是重量级的,因为要返回桩数据,所以有很多复杂的操作
- (3) 相比之下,空对象的目的明确的多,也巧妙、智能得多
9.接口与类型信息
- 接口并不能保护客户端不调用实现类中除public以外的其他修饰符的方法
哪怕是private的方法或字段,都可以使用Method.setAccessible(true)之后访问。 - 利用反射可以无视包访问权限、私有内部类、匿名类,调用其中接口外的方法
还没有评论,来说两句吧...