RTTI与反射之Java

一、引言 

  很多时候我们的程序可能需要在运行时识别对象和类的信息,比如多态就是基于运行时环境进行动态判断实际引用的对象。在运行时识别对象和类的信息主要有两种方式:1.RTTI,具体是Class对象,它假定我们在编译时已经知道了所有类型。2.反射机制,运行我们在运行时发现和使用类的信息。

二、RTTI

  RTTI(Run-Time Type Infomation),运行时类型信息。可以在运行时识别一个对象的类型。类型信息在运行时通过Class对象表示,Class对象包含与类有关的信息,可以使用Class对象来创建类的实例。

  每个类对应一个Class对象,这个Class对象放在.class文件中,当我们的程序中首次主动使用某类型时,会把该类型所对应的Class对象加载进内存,在这篇文章JVM之类加载器中阐述了哪些情况符合首次主动使用。

  既然RTTI和Class对象有莫大的关系,即有了Class对象,就可以进行很多操作,那么,我们如何获取到Class对象呢?有三种方法1. Class.forName("全限定名");(其中,全限定名为包名+类名)。2. 类字面常量,如String.class,对应String类的Class对象。3.通过getClass()方法获取Class对象,如String str = "abc";str.getClass();。

  通过一个类对应的Class对象后,我们可以做什么?我们可以获取该类的父类、接口、创建该类的对象、该类的构造器、字段、方法等等。总之,威力相当大。

  下面我们通过一个例子来熟悉Class对象的各种用法。

package com.hust.grid.leesf.algorithms;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

interface SuperInterfaceA {
};

interface SuperInterfaceB {
};

class SuperC {
    private String name;

    public SuperC() {

    }

    public SuperC(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

class Sub extends SuperC implements SuperInterfaceA, SuperInterfaceB {
    private String name;
    public Sub() {
        super();
    }

    public Sub(String name) {
        super(name);
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class Main {
    public static Sub makeInstance(Class<?> clazz) {
        Sub sub = null;
        try {
            sub = (Sub) clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return sub;
    }

    public static void printBasicInfo(Class<?> clazz) {
        System.out.println("CanonicalName : " + clazz.getCanonicalName());
        System.out.println("Name : " + clazz.getName());
        System.out.println("Simple Name : " + clazz.getSimpleName());
        System.out.println("SuperClass Name : "
                + clazz.getSuperclass().getName());
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> inter : interfaces) {
            System.out.println("Interface SimpleName : "
                    + inter.getSimpleName());
        }
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> cons : constructors) {
            System.out.println("Constructor Name : " + cons.getName()
                    + " And Parameter Count : " + cons.getParameterCount());
        }
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("Method Name : " + method.getName());
        }
    }

    public static void main(String[] args) {
        //Sub sub = new Sub();
        //Class<?> clazz = sub.getClass();
        Class<?> clazz = Sub.class;
        Sub instance = makeInstance(clazz);
        if (instance != null) {
            System.out.println("make instance successful");
        } else {
            System.out.println("make instance unsuccessful");
        }
        printBasicInfo(clazz);
    }
}

  运行结果: 

make instance successful
CanonicalName : com.hust.grid.leesf.algorithms.Sub
Name : com.hust.grid.leesf.algorithms.Sub
Simple Name : Sub
SuperClass Name : com.hust.grid.leesf.algorithms.SuperC
Interface SimpleName : SuperInterfaceA
Interface SimpleName : SuperInterfaceB
Constructor Name : com.hust.grid.leesf.algorithms.Sub And Parameter Count : 0
Constructor Name : com.hust.grid.leesf.algorithms.Sub And Parameter Count : 1
Method Name : getName

  说明:使用method1、method2、method3三种方法都可以获得Class对象,运行结果是等效的。但是三者还是有稍许的区别。区别是从类的初始化角度来看的。如Class.forName("全限定名")会导致类型的加载、链接、初始化过程,而.class则不会初始化该类。显然,getClass肯定是会初始化该类的,因为这个方法时依托于类的对象。

  下面我们通过一个例子比较.class和forName()两种方法的区别。

package com.hust.grid.leesf.algorithms;
import java.util.Random;
class Init1 {
    static final int staticFinal1 = 1;
    static final int staticFinal2 = Main.random.nextInt(100);
    static {
        System.out.println("init init1");
    }
}

class Init2 {
    static int staticNonFinal1 = 3;
    static {
        System.out.println("init init2");
    }
}

class Init3 {
    static int staticNonFinal1 = 5;
    static {
        System.out.println("init init3");
    }
}

public class Main {
    public static Random random = new Random(47);
    public static void main(String[] args) {
        Class<?> clazzClass = Init1.class;
        System.out.println("after init init1 ref");
        System.out.println(Init1.staticFinal1);
        System.out.println(Init1.staticFinal2);

        System.out.println(Init2.staticNonFinal1);
        try {
            Class<?> clazz1 = Class.forName("com.hust.grid.leesf.algorithms.Init3");
            System.out.println("after init init3 ref");
            System.out.println(Init3.staticNonFinal1);
        } catch (ClassNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
    }
}

  运行结果:

after init init1 ref
1
init init1
58
init init2
3
init init3
after init init3 ref
5

  说明:从结果也进一步验证了.class不会初始化类,而.forName()会初始化类。并且,对常量静态域的使用也不会导致类的初始化。

三、反射

  与RTTI必须在编译器就知道所有类型不同,反射不必在编译期就知道所有的类型,它可以在运行过程中使用动态加载的类,而这个类不必在编译期就已经知道。反射主要由java.lang.reflect类库的Field、Method、Constructor类支持。这些类的对象都是JVM在运行时进行创建,用来表示未知的类。

  关于两者的区别更深刻表达如下:对于RTTI而言,编译器在编译时打开和检查.class文件;对于反射而言,.class文件在编译时是不可获取的,所以在运行时打开和检查.class文件。

  其实在的第一个例子中我们已经用到了Constructor、Method类,现在我们来更加具体的了解Constructor、Method、Field类。  

package com.hust.grid.leesf.algorithms;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

class Human {

}

class Girl extends Human {
    private boolean beautiful;
    private int height;
    private String name;

    public Girl() {

    }

    public Girl(String name, int height, boolean beautiful) {
        this.name = name;
        this.height = height;
        this.beautiful = beautiful;
    }

    public boolean isBeautiful() {
        return beautiful;
    }

    public String toString() {
        return "height = " + height + " name = " + name + " beautiful = " + beautiful;
    }

    private void print() {
        System.out.println("i am a private method");
    }
}

class Boy extends Human {
    private boolean handsome;
    private int height;
    private String name;
    public Boy() {

    }

    public Boy(String name, int height, boolean handsome) {
        this.name = name;
        this.height = height;
        this.handsome = handsome;
    }

    public boolean isHandsome() {
        return handsome;
    }

    public String toString() {
        return "height = " + height + " name = " + name + " handsome = " + handsome;
    }

    private void print() {
        System.out.println("i am a private method");
    }
}

public class Test {
    public static void main(String[] args) throws NoSuchMethodException,
            SecurityException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException,
            NoSuchFieldException {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        Human human = null;
        String name = "leesf";
        int height = 180;
        boolean handsome = true;
        boolean flag = false;
        if ("boy".equals(input)) {
            human = new Boy(name, height, handsome);
            flag = true;
        } else {
            human = new Girl("dyd", 168, true);
        }
        scanner.close();
        Class<?> clazz = human.getClass();
        Constructor<?> constructor = clazz.getConstructor(String.class,
                int.class, boolean.class);
        Human human1 = (Human) constructor.newInstance("leesf_dyd", 175, true);
        System.out.println(human1);
        Method method = null;
        if (flag) {
            method = clazz.getMethod("isHandsome");
        } else {
            method = clazz.getMethod("isBeautiful");
        }
        System.out.println(method.invoke(human));
        Method method2 = clazz.getDeclaredMethod("print");
        method2.setAccessible(true);
        method2.invoke(human);

        Field field = clazz.getDeclaredField("height");
        System.out.println(human);
        field.setAccessible(true);
        field.set(human, 200);
        System.out.println(human);
    }
}

  输入:boy

  运行结果: 

boy
height = 175 name = leesf_dyd handsome = true
true
i am a private method
height = 180 name = leesf handsome = true
height = 200 name = leesf handsome = true

  说明:反射可以让我们创建一个类的实例、在类外部访问类的私有方法、私有字段。反射真的很强大~

四、动态代理-反射的应用

  动态创建代理并且动态处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上。

  下面是动态代理的例子

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
    void doSomething();

    void doSomethingElse(String str);
}

class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void doSomethingElse(String str) {
        System.out.println("doSomething else " + str);
    }
}

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 {
        if (method.getName().startsWith("do")) {
            System.out.println("call do*** methods");
        }
        method.invoke(proxied, args);
        return null;
    }

}

public class DynamicProxy {
    public static void main(String[] args) {
        RealObject proxied = new RealObject();
        proxied.doSomething();
        proxied.doSomethingElse("leesf");
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class
                .getClassLoader(), new Class[] { Interface.class },
                new DynamicProxyHandler(proxied));
        proxy.doSomething();
        proxy.doSomethingElse("leesf");
    }
}

  运行结果: 

doSomething
doSomething else leesf
call do*** methods
doSomething
call do*** methods
doSomething else leesf

  说明:可以在invoke方法中进行过滤操作。过滤出以do开头的方法进行转发。

五、总结

  RTTI和反射分析就到此为止,RTTI和反射确实很强大,可以帮助我们干很多事情,用对地方绝对威力无穷,谢谢各位园友的观看~

  

时间: 2024-10-26 17:15:51

RTTI与反射之Java的相关文章

【Java基础】RTTI与反射之Java

一.引言 很多时候我们的程序可能需要在运行时识别对象和类的信息,比如多态就是基于运行时环境进行动态判断实际引用的对象.在运行时识别对象和类的信息主要有两种方式:1.RTTI,具体是Class对象,它假定我们在编译时已经知道了所有类型.2.反射机制,运行我们在运行时发现和使用类的信息. 二.RTTI RTTI(Run-Time Type Infomation),运行时类型信息.可以在运行时识别一个对象的类型.类型信息在运行时通过Class对象表示,Class对象包含与类有关的信息,可以使用Clas

Java系列笔记(2) - Java RTTI和反射机制

目录 前言 传统的RTTI 反射 反射的实现方式 反射的性能 反射与设计模式 前言 并不是所有的Class都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程,),这就是RTTI(Runtime Type Information,运行时类型信息). 在java中,有两种RTTI的方式,一种是传统的,即假设在编译时已经知道了所有的类型:还有一种,是利用反射机制,在运行时再尝试确定类型信息. 本文主要讲反射方式实现的RTTI,建议在阅读本文之前,先了解类的加载机制(

Java基础:RTTI与反射

转载请注明出处:jiq?钦's technical Blog 1 Class文件: class文件全名称为Javaclass文件,主要在平台无关性和网络移动性方面使Java更适合网络.它在平台无关性方面的任务是:为Java程序提供独立于底层主机平台的二进制形式的服务. 每一个类都有一个Class类型的对象,每当一个类被编译,就会产生一个Class对象(保存在同名的.class文件中): 2 Class文件加载时机: 运行时按需加载. 与普通程序不同的是,Java程序(class文件)并不是本地的

java之RTTI和反射的理解

最近在读 Thinking in Java 这本书.读到类型信息这一张时,刚开始对书中所说的RTTI和反射彻底混了,不知道有什么联系,有哪些相同点和区别.于是在网上又找了些内容辅助理解,这一章又重新读了一遍,对上面的这些疑问有了自己的答案,想到RTTI和反射时,脑海中有了自己的轮廓. 它俩的目的一样: 在运行时,识别对象和类的信息. 相同点: 目的相同: 功能都是通过Class类来实现的 不同点: RTTI:(主要作用在多态中,比如参数传递) 它假定我们在编译时已经知道所有类型,会打开和检查所有

13. Java基础之类型信息(RTTI和反射)

一. 背景 并不是所有的Class都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程,),这就是RTTI(Runtime Type Information,运行时类型信息). Java是如何让我们在运行时识别对象和类的信息的,主要有两种RTTI的方式,一种是"传统的"RTTI,即假设在编译时已经知道了所有的类型:还有一种,是利用反射机制,在运行时再尝试确定类型信息. 二. RTTI RTTI(Run-Time Type Infomation),运行时

RTTI和反射小结

Java有两种方式让我们在运行时识别对象和类的信息:1."传统的"RTTI,假定所有的类型编译时已知:2."反射"机制,允许在运行时发现和使用类的信息. 一.RTTI RTTI(Run-Time Type Infomation),在运行时识别一个对象的信息.RTTI的形式有三种: 1.传统的类型转换 如"Shape s=(Shape) new Circle()",由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个Class

QObject提供了QMetaObject元类信息(相当于RTTI和反射),信号与连接,父子关系,调试信息,属性,事件,继承关系,窗口类型,线程属性,时间器,对象名称,国际化

元类信息(相当于RTTI和反射),信号与连接,父子关系,调试信息,属性,事件,继承关系,窗口类型,线程属性,时间器,对象名称,国际化其中元类又提供了:classInfo,className,构造函数,多重祖先元类,method, property, Enumerator, Signal, Slot等等 http://doc.qt.io/qt-5/qobject.html http://doc.qt.io/qt-5/qmetaobject.html 我感觉Qt的出现,除了提供GUI以外,主要就是提

moon 反射机制---java.lang.reflect包

java反射机制:在运行状态中,对于一个已经加载到JVM的java对象/类 在程序中实现访问.检查.修改.描述java对象本身的信息(构造方法.方法.成员变量.类本身的信息) 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. C++,Java,C#不是动态语言.但是JAVA有着一个非常突出的动态相关机制:Reflection, 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接. 二,反射机制的作用:

java的RTTI和反射机制

RTTI,即Run-Time Type Identification,运行时类型识别.它假定我们在编译时已经知道了所有的类型.那么在运行时就能够自动识别每个类型. 很多时候需要进行向上转型,比如Base类派生出Derived类,但是现有的方法只需要将Base对象作为参数,实际传入的则是其派生类的引用.那么RTTI就在此时起到了作用,比如通过RTTI能识别出Derive类是Base的派生类,这样就能够向上转型为Derived.类似的,在用接口作为参数时,向上转型更为常用,RTTI此时能够判断是否可