【Java基础】RTTI与反射之Java

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

一、引言 

  很多时候我们的程序可能需要在运行时识别对象和类的信息,比如多态就是基于运行时环境进行动态判断实际引用的对象。在运行时识别对象和类的信息主要有两种方式: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类。  


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和反射确实很强大,可以帮助我们干很多事情,用对地方绝对威力无穷,谢谢各位园友的观看~


目录
相关文章
|
9月前
|
Java 开发者
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
341 0
|
5月前
|
存储 Java 程序员
Java 基础知识点全面梳理包含核心要点及难点解析 Java 基础知识点
本文档系统梳理了Java基础知识点,涵盖核心特性、语法基础、面向对象编程、数组字符串、集合框架、异常处理及应用实例,帮助初学者全面掌握Java入门知识,提升编程实践能力。附示例代码下载链接。
221 1
|
7月前
|
IDE Java 开发工具
【Java基础-环境搭建-创建项目】IntelliJ IDEA创建Java项目的详细步骤
IntelliJ IDEA创建Java项目的图文详细步骤,手把手带你创建Java项目
1227 10
【Java基础-环境搭建-创建项目】IntelliJ IDEA创建Java项目的详细步骤
|
6月前
|
存储 安全 Java
2025 年最新 40 个 Java 基础核心知识点全面梳理一文掌握 Java 基础关键概念
本文系统梳理了Java编程的40个核心知识点,涵盖基础语法、面向对象、集合框架、异常处理、多线程、IO流、反射机制等关键领域。重点包括:JVM运行原理、基本数据类型、封装/继承/多态三大特性、集合类对比(ArrayList vs LinkedList、HashMap vs TreeMap)、异常分类及处理方式、线程创建与同步机制、IO流体系结构以及反射的应用场景。这些基础知识是Java开发的根基,掌握后能为后续框架学习和项目开发奠定坚实基础。文中还提供了代码资源获取方式,方便读者进一步实践学习。
1741 2
|
6月前
|
存储 安全 Java
Java 基础知识面试题汇总 最全面的 Java 基础面试题整理
本文全面解析Java基础知识面试题,涵盖Java基础概念、面向对象编程、异常处理、集合框架等核心内容。通过实际应用场景,提供技术方案与应用实例,如JDK与JRE区别、==与equals()差异、String类特性、final与static关键字用法、多继承替代方案及接口与抽象类对比。帮助开发者夯实基础,高效备考,提升实战能力。附带完整代码示例,可供下载学习。
777 3
|
9月前
|
设计模式 缓存 Java
重学Java基础篇—Java对象创建的7种核心方式详解
本文全面解析了Java中对象的创建方式,涵盖基础到高级技术。包括`new关键字`直接实例化、反射机制动态创建、克隆与反序列化复用对象,以及工厂方法和建造者模式等设计模式的应用。同时探讨了Spring IOC容器等框架级创建方式,并对比各类方法的适用场景与优缺点。此外,还深入分析了动态代理、Unsafe类等扩展知识及注意事项。最后总结最佳实践,建议根据业务需求选择合适方式,在灵活性与性能间取得平衡。
583 3
|
9月前
|
安全 IDE Java
重学Java基础篇—Java泛型深度使用指南
本内容系统介绍了Java泛型的核心价值、用法及高级技巧。首先阐述了泛型在**类型安全**与**代码复用**中的平衡作用,解决强制类型转换错误等问题。接着详细讲解了泛型类定义、方法实现、类型参数约束(如边界限定和多重边界)、通配符应用(PECS原则)以及类型擦除的应对策略。此外,还展示了泛型在通用DAO接口、事件总线等实际场景的应用,并总结了命名规范、边界控制等最佳实践。最后探讨了扩展知识,如通过反射获取泛型参数类型。合理运用泛型可大幅提升代码健壮性和可维护性,建议结合IDE工具和单元测试优化使用。
334 1
|
9月前
|
安全 IDE Java
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
266 1
|
11月前
|
存储 移动开发 算法
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
282 1
|
12月前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。