RTTI(Runtime Type Identification)

322 阅读3分钟

运行时类型信息使得你可以在程序运行时发现和使用类型信息

Java多态,实现接口方法动态绑定的基础。

Class对象

每一个类都有一个class对象,它包含了与类有关的信息。Java使用class对象来执行RTTI。

类初始化(区别于对象初始化构造)

时机

注意:在使用XXX.class的形式,创建class对象的引用时,并不会自动的初始化该Class对象

  1. 加载

    由类加载器,查找字节码。

  2. 链接

  • 验证类的字节码
  • 静态域分配存储空间
  • 解析类中对其他类的引用
  1. 初始化

    初始化并不会自动进行。当有对类的静态方法,或者非常数静态域进行首次引用的时候才执行

    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
    

示例注意点:

  1. 使用.class的方式,不会造成类的初始化,而使用Class.forName()会。
  2. 访问类的static常量并不会造成类的初始化。
  3. 使用字面量常量:Initable.class,相比使用Class.forName(),更加的安全和简洁。因为它在编译期时,就受到了安全检查,不需要置于try语句块中。所以Class.forName(),时在编译期无法访问到字面量常量的时候,才应该使用,就是下面会聊到的反射。

泛化的class引用

  1. Class优于平凡的class,即使他们是等价的,Class的好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本。
  2. 向Class引入泛型语法的原因仅仅是为了提供编译期类型检查,防止你的编码犯错。

instanceof与Class的等价性

instanceof保持了类型的概念,它指的是“你是这个类吗。或者你是这个类的派生类吗?”而如果用==,或者equals比较实际的class对象,就没有考虑继承--它或者是这个确切的类型,或者不是。

反射

前面的内容,我们知道,RTTI是通过加载类的Class对象,通过这个Class对象来了解具体的类型信息。但如果在编译期时.class文件是不可获取的,那我们想在运行时获取这个类的类型信息,则需要在运行时打开和检查class文件。这就是反射机制。

  1. 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
  1. 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内部,我们可以实现自己的代理逻辑。