运行时类型信息使得你可以在程序运行时发现和使用类型信息
Java多态,实现接口方法动态绑定的基础。
Class对象
每一个类都有一个class对象,它包含了与类有关的信息。Java使用class对象来执行RTTI。
类初始化(区别于对象初始化构造)
时机
注意:在使用XXX.class的形式,创建class对象的引用时,并不会自动的初始化该Class对象
-
加载
由类加载器,查找字节码。
-
链接
- 验证类的字节码
- 静态域分配存储空间
- 解析类中对其他类的引用
-
初始化
初始化并不会自动进行。当有对类的静态方法,或者非常数静态域进行首次引用的时候才执行
public class Initable { static final int staticFinal = 47; static final int staticFinal2 = new Random(47).nextInt(); static { System.out.println("Initializing initable"); } } public class Initable2 { static int staticNonFinal = 147; static { System.out.println("Initializing initable2"); } } public class Initable3 { static int staticNonFinal = 73; static { System.out.println("Initializing initable3"); } } public class ClassInitialization { public static void main(String[] args){ Class initable = Initable.class; System.out.println("After creating Initable ref"); System.out.println(Initable.staticFinal); System.out.println(Initable.staticFinal2); System.out.println(Initable2.staticNonFinal); try{ Class initable3 = Class.forName("init.Initable3"); }catch (Exception e){} System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } } After creating Initable ref 47 Initializing initable -1172028779 Initializing initable2 147 Initializing initable3 After creating Initable3 ref 73
示例注意点:
- 使用.class的方式,不会造成类的初始化,而使用Class.forName()会。
- 访问类的static常量并不会造成类的初始化。
- 使用字面量常量:Initable.class,相比使用Class.forName(),更加的安全和简洁。因为它在编译期时,就受到了安全检查,不需要置于try语句块中。所以Class.forName(),时在编译期无法访问到字面量常量的时候,才应该使用,就是下面会聊到的反射。
泛化的class引用
- Class优于平凡的class,即使他们是等价的,Class的好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本。
- 向Class引入泛型语法的原因仅仅是为了提供编译期类型检查,防止你的编码犯错。
instanceof与Class的等价性
instanceof保持了类型的概念,它指的是“你是这个类吗。或者你是这个类的派生类吗?”而如果用==,或者equals比较实际的class对象,就没有考虑继承--它或者是这个确切的类型,或者不是。
反射
前面的内容,我们知道,RTTI是通过加载类的Class对象,通过这个Class对象来了解具体的类型信息。但如果在编译期时.class文件是不可获取的,那我们想在运行时获取这个类的类型信息,则需要在运行时打开和检查class文件。这就是反射机制。
- Class.getMethod(),获取类和父类及接口的Pulibc方法
public class ReflectionTest {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("init.ReflectionTest");
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
public void aMethod() {
}
void bMethod() {
System.out.println("bMethod invoked");
}
private void cMethod() {
System.out.println("cMethod invoked");
}
}
// Output
main
aMethod
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
- Class.getDeclaredMethods(),获取当前类中声明的方法
public class ReflectionTest {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("init.ReflectionTest");
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
public void aMethod() {
}
void bMethod() {
System.out.println("bMethod invoked");
}
private void cMethod() {
System.out.println("cMethod invoked");
}
}
// Output
main
aMethod
bMethod
cMethod
反射也并不是毫无限制的,它无法脱离Java安全机制。可以通过定义自己的安全策略去限制反射的行为。 Java安全:SecurityManager与AccessController - 掘金 (juejin.cn)
动态代理
普通代理模式
public interface DoSomeInterface {
public void doSomeMethod();
}
public class DoSomeInterfaceImpl implements DoSomeInterface {
@Override
public void doSomeMethod() {
System.out.println("do something");
}
}
public class DoSomeProxy implements DoSomeInterface {
private DoSomeInterface realObject;
public DoSomeProxy(DoSomeInterface realObject) {
this.realObject = realObject;
}
@Override
public void doSomeMethod() {
System.out.println("Do Some else");
realObject.doSomeMethod();
}
}
拥有反射的能力后,我们就不需要每次都实现一个代理的接口类了。Java给我们提供了一套动态代理的框架。
public class DoSomeInvokeHandler implements InvocationHandler {
private final DoSomeInterface doSomeInterface;
public DoSomeInvokeHandler(DoSomeInterface object) {
doSomeInterface = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("doSomeMethod")) {
System.out.println("Do Some else");
}
return method.invoke(doSomeInterface, args);
}
public static void main(String[] args) {
DoSomeInterface proxy = (DoSomeInterface) Proxy.newProxyInstance(DoSomeInterface.class.getClassLoader(), new Class[]{DoSomeInterface.class}, new DoSomeInvokeHandler(new DoSomeInterfaceImpl()));
proxy.doSomeMethod();
}
}
在执行代理对象的任何方法,都会变成支持invoke方法。在invoke内部,我们可以实现自己的代理逻辑。