Java运行时类型信息(Run-Time Type Identification)使得你可以在程序运行时发现和使用类型信息。
——《Java编程思想》
1.类型信息与多态
??面向对象编程中的基本目的是:让代码只操纵对基类的引用,如示例中的Animal
。这里实际上是将Dog
和Cat
向上转型为Animal
,在之后的程序使用中完成了“匿名”。另外,Java中所有的类型转换都是在运行时进行正确性检查,这也是RTTI最基本的使用形式,即:在运行时识别一个对象的类型。
e.g.
// Animal类
public abstract class Animal {
abstract void getName();
}
// Dog类
public class Dog extends Animal {
@Override
void getName() {
System.out.println("Dog");
}
}
// Cat类
public class Cat extends Animal {
@Override
void getName() {
System.out.println("Cat");
}
}
2.Class对象
??每个类都有一个特殊的Class
对象,它包含了与类有关的信息,Class
对象由类装在子系统生成。
获取类的Class
实例的三种方法:
- 通过静态变量
class
获取
Class cls = String.class;
- 使用
getClass()
获取实例的类型信息
String s = "String";
Class cls = s.getClass();
- 通过类的完整类名获取
try {
Class c = Class.forName("blog.Dog");
} catch (Exception e) {
e.printStackTrace();
}
通常使用Class<? extends Animal>
,通过通配符结合继承关系来限定范围。
3.instanceof
??instanceof
返回一个boolean
值,用于判断一个对象是不是指定类型的实例或接口的实现。而使用==
可以精确判断数据类型。
4.反射
反射可以说是Java中最重要的技术之一了,整个Spring框架都大量依赖于反射技术。
反射是一种能够在程序运行时获取类信息的机制。Class
类与java.lang.reflect
类库一起对反射的概念进行了支持,该类库包含了Field
、Method
和Constructor
类。反射机制的实现逻辑是:当程序通过反射与一个未知类型的对象打交道时,JVM会检查这个对象,看它属于哪个特定的类。
// 获取字段信息的方法
public class Main {
public static void main(String[] args) throws Exception {
Class cls = Student.class;
// 获取父类public字段
Field name = cls.getField("name");
Field age = cls.getField("age");
// 获取私有字段
Field phone = cls.getDeclaredField("phone");
// 获取字段名称
String fieldName = name.getName();
// 获取字段类型
Class fieldType = name.getType();
// 获取字段修饰符
int fieldModifier = name.getModifiers();
Person std = new Student(20, "110119120");
int stdAge = (int) age.get(std);
// 获取private字段的值
phone.setAccessible(true);
String stdPhone = (String) phone.get(std);
// 设置字段的值
age.set(std, 18);
// 获取父类
Class superCls = cls.getSuperclass();
// 获取当前类实现的所有接口
Class[] interfaceCls = cls.getInterfaces();
}
}
访问方法和构造方法都是类似的,具体的方法名参考Method
和Constructor
类。
5.动态代理
代理是基本设计模式,它可以提供额外的操作。实际上,日志之类的可以通过代理类这种方式实现,只是代码冗余量会非常大。Proxy.newProxyInstance()
可以创建动态代理,它的第一个参数是一个类加载器,在示例代码中也传入了Easy.class
参数,这并不意味着要用这个类加载器去实例化接口,接口是不能实例化的,代理实际上是JVM在运行期动态创建class字节码
并加载的过程,而加载类需要类加载器。
接口
public interface Easy {
void doSomething();
void somethingElse(String s);
}
实现类
public class SayHello implements Easy {
@Override
public void doSomething() {
System.out.println("Hello");
}
@Override
public void somethingElse(String s) {
System.out.println("Hello " + s);
}
}
代理类
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);
return method.invoke(proxied, args);
}
}
启动类
public class DynamicProxy {
public static void main(String[] args) {
SayHello sayHello = new SayHello();
consumer(sayHello);
// 创建动态代理
Easy proxy = (Easy) Proxy.newProxyInstance(Easy.class.getClassLoader(),
new Class[] { Easy.class },
new DynamicProxyHandler(sayHello));
consumer(proxy);
}
public static void consumer(Easy easy) {
easy.doSomething();
easy.somethingElse("XXX");
}
}