zoukankan      html  css  js  c++  java
  • 通过反射查看类信息

    Java 程序中的许多对象在运行时都会出现两种类型:编译时类型和运行时类型,例如代码:

    Person p = new Student();

    这行代码将会生成一个p变量,该变量的编译时类型为 person,运行时类型为 Student;除此之外,还有更极端的情形,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是 Object,但程序又需要调用该对象运行时类型的方法。

    为了解决这些问题,程序需要在运行时发现对象和类的真实信息。解决该问题有以下两种做法。

    • 第一种做法是假设在编译时和运行时都完全知道类型的具体信息,在这种情况下,可以先使用 instanceof 运算符进行判断,再利用强制类型转换将其转换成其运行时类型的变量即可。
    • 第二种做法是编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。

    获得 Class 对象

    前面已经介绍过了,每个类被加载之后,系统就会为该类生成一个对应的 Class 对象,通过该 Class对象就可以访问到 JVM 中的这个类。在 Java 程序中获得 Class 对象通常有如下三种方式。

    • 使用 Class 类的 forName(String clazzName) 静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。
    • 调用某个类的 class 属性来获取该类对应的 Class 对象。例如,Person.class 将会返回 Person 类对应的 Class 对象。
    • 调用某个对象的 getClass() 方法。该方法是 java.lang.Object 类中的一个方法,所以所有的 Java 对象都可以调用该方法,该方法将会返回该对象所属类对应的 Class 对象。

    对于第一种方式和第二种方式都是直接根据类来取得该类的 Class 对象,相比之下,第二种方式有如下两种优势。

    • 代码更安全。程序在编译阶段就可以检查需要访问的 Class 对象是否存在。
    • 程序性能更好。因为这种方式无须调用方法,所以性能更好。

    也就是说,大部分时候都应该使用第二种方式来获取指定类的 Class 对象。但如果程序只能获得一个字符串,例如 "java.lang.String",若需要获取该字符串对应的 Class 对象,则只能使用第一种方式,使用 Class 的 forName(String clazzName) 方法获取 Class 对象时,该方法可能抛出一个 ClassNotFoundException 异常。

    一旦获得了某个类所对应的 Class 对象之后,程序就可以调用 Class 对象的方法来获得该对象和该类的真实信息了。

    从 Class 中获取信息

    Class 类提供了大量的实例方法来获取该 Class 对象所对应类的详细信息,Class 类大致包含如下方法,下面每个方法都可能包括多个重载的版本,读者应该查阅API文档来掌握它们。

    下面4个方法用于获取 Class 对应类所包含的构造器。

    • Constructor<T> getConstructor(Class<?>...parameterTypes):返回此 Class 对象对应类的、带指定形参列表的 public 构造器。
    • Constructor<?>[] getConstructor():返回此 Class 对象对应类的所有 public 构造器。
    • Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes):返回此 Class 对象对应类的、带指定形参列表的构造器,与构造器的访问权限无关。
    • Constructor<?>[] getDeclaredConstructors():返回此 Class 对象对应类的所有构造器,与构造器的访问权限无关。

    下面4个方法用于获取 Class 对应类所包含的方法。

    • Method getMethod(String name, Class<?>...parameterTypes):返回此 Class 对象对应类的、带指定形参列表的 public 方法。
    • Method[] getMethods():返回此 Class 对象所表示的类的所有 public 方法。
    • Method getDeclaredMethod(String name, Class<?>...parameterTypes):返回此Class对象对应类的、带指定形参列表的方法,与方法的访问权限无关。
    • Method[] getDeclaredMethods():返回此 Class 对象对应类的全部方法,与方法的访问权限无关。

    如下4个方法用于访问 Class 对应类所包含的成员变量。

    • Field getField(String name):返回此 Class 对象对应类的、指定名称的 public 成员变量。
    • Field[] getFieIds():返回此 Class 对象对应类的所有 public 成员变量。
    • Field getDeclaredField(String name):返回此 Class 对象对应类的、指定名称的成员变量,与成员变量的访问权限无关。
    • Field[] getDeclaredFields():返回此 Class 对象对应类的全部成员变量,与成员变量的访问权限无关。

    如下几个方法用于访问 Class 对应类上所包含的 Annotation。

    • <A extends Annotation> A getAnnotation(Class<A> annotationClass):尝试获取该 Class 对象对应类上存在的、指定类型的 Annotation;如果该类型的注解不存在,则返回null.
    • <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass):这是 Java8 新增的方法,该方法尝试获取直接修饰该 Class 对象对应类的、指定类型的 Annotation;如果该类型的注解不存在,则返回null。
    • Annotation[] getAnnotations():返回修饰该 Class 对象对应类上存在的所有 Annotation。
    • Annotation[] getDeclaredAnnotations():返回直接修饰该 Class 对应类的所有 Annotation。
    • <A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的 getAnnotation() 方法基本相似。但由于 Java8 增加了重复注解功能,因此需要使用该方法获取修饰该类的、指定类型的多个 Annotation。
    • <A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的 getDeclaredAnnotations() 方法基本相似。但由于 Java8 增加了重复注解功能,因此需要使用该方法获取直接修饰该类的、指定类型的多个Annotation。

    如下方法用于访问该 Class 对象对应类包含的内部类。

    • Class<?>[] getDeclaredClasses():返回该 Class 对象对应类里包含的全部内部类。

    如下方法用于访问该 Class 对象对应类所在的外部类。

    • Class<?> getDeclaringClass():返回该 Class 对象对应类所在的外部类。

    如下方法用于访问该 Class 对象对应类所实现的接口。

    • Class<?>[] getInterfaces():返回该 Class 对象对应类所实现的全部接口。

    如下几个方法用于访问该 Class 对象对应类所继承的父类。

    • Class<? super T> getSuperclass():返回该 Class 对象对应类的超类的 Class 对象。

    如下方法用于获取 Class 对象对应类的修饰符、所在包、类名等基本信息。

    • int getModifiers():返回此类或接口的所有修饰符。修饰符由public、protected、private、final、static、abstract等对应的常量组成,返回的整数应使用 Modifier 工具类的方法来解码,才可以获取真实的修饰符。
    • Package getPackage():获取此类的包。
    • String getName():以字符串形式返回此 Class 对象所表示的类的名称。
    • String getSimpleName():以字符串形式返回此 Class 对象所表示的类的简称。

    除此之外,Class 对象还可调用如下几个判断方法来判断该类是否为接口、枚举、注解类型等。

    • boolean isAnnotation():返回此 Class 对象是否表示一个注解类型(由 @interface 定义)。
    • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断此 Class 对象是否使用了 Annotation 修饰。
    • boolean isAnonymousClass():返回此 Class 对象是否是一个匿名类
    • boolean isArray():返回此 Class 对象是否表示一个数组类。
    • boolean isEnum():返回此 Class 对象是否表示一个枚举(由 enum 关键字定义)。
    • boolean isInterface():返回此 Class 对象是否表示一个接口(使用 interface 定义)。
    • boolean isInstance(Object obj):判断 obj 是否是此 Class 对象的实例,该方法可以完全代替 instanceof 操作符。

    上面的多个 getMethod() 方法和 getConstructor() 方法中,都需要传入多个类型为 Class<?> 的参数,用于获取指定的方法或指定的构造器。关于这个参数的作用,假设某个类内包含如下三个 info 方法签名。

    • public void info()
    • public void info(String str)
    • public void info(String str, Integer num)

    这三个同名方法属于重载,它们的方法名相同,但参数列表不同。在 Java 语言中要确定一个方法光有方法名是不行的,如果仅仅只指定 info 方法——实际上可以是上面三个方法中的任意一个!如果需要确定一个方法,则应该由方法名和形参列表来确定,但形参名没有任何实际意义,所以只能由形参类型来确定。例如想指定第二个 info 方法,则必须指定方法名为 info,形参列表为 String.class 因此在程序中获取该方法使用如下代码:

    //前一个参数指定方法名,后面的个数可变的 Class 参数指定形参类型列表
    clazz.getMethOd("info", String.class)
    //如果需要获取第三个info方法,则使用如下代码;
    //前一个参数指定方法名,后面的个数可变的 Class 参数指定形参类型列表
    clazz.getMethOd("infO", String.class, Integer.class)

    获取构造器时无须传入构造器名——同一个类的所有构造器的名字都是相同的,所以要确定一个构造器只要指定形参列表即可。

    下面程序示范了如何通过该 Class 对象来获取对应类的详细信息。

    // 定义可重复注解
    @Repeatable(Annos.class)
    @interface Anno {
    }
    
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface Annos {
        Anno[] value();
    }
    
    // 使用4个注解修饰该类
    @SuppressWarnings(value = "unchecked")
    @Deprecated
    // 使用重复注解修饰该类
    @Anno
    @Anno
    public class ClassTest {
        // 为该类定义一个私有的构造器
        private ClassTest() {
        }
    
        // 定义一个有参数的构造器
        public ClassTest(String name) {
            System.out.println("执行有参数的构造器");
        }
    
        // 定义一个无参数的info方法
        public void info() {
            System.out.println("执行无参数的info方法");
        }
    
        // 定义一个有参数的info方法
        public void info(String str) {
            System.out.println("执行有参数的info方法" + ",其str参数值:" + str);
        }
    
        // 定义一个测试用的内部类
        class Inner {
        }
    
        public static void main(String[] args) throws Exception {
            // 下面代码可以获取ClassTest对应的Class
            Class<ClassTest> clazz = ClassTest.class;
            // 获取该Class对象所对应类的全部构造器
            Constructor[] ctors = clazz.getDeclaredConstructors();
            System.out.println("ClassTest的全部构造器如下:");
            for (Constructor c : ctors) {
                System.out.println(c);
            }
            // 获取该Class对象所对应类的全部public构造器
            Constructor[] publicCtors = clazz.getConstructors();
            System.out.println("ClassTest的全部public构造器如下:");
            for (Constructor c : publicCtors) {
                System.out.println(c);
            }
            // 获取该Class对象所对应类的全部public方法
            Method[] mtds = clazz.getMethods();
            System.out.println("ClassTest的全部public方法如下:");
            for (Method md : mtds) {
                System.out.println(md);
            }
            // 获取该Class对象所对应类的指定方法
            System.out.println("ClassTest里带一个字符串参数的info()方法为:" + clazz.getMethod("info", String.class));
            // 获取该Class对象所对应类的上的全部注解
            Annotation[] anns = clazz.getAnnotations();
            System.out.println("ClassTest的全部Annotation如下:");
            for (Annotation an : anns) {
                System.out.println(an);
            }
            System.out.println("该Class元素上的@SuppressWarnings注解为:"
                    + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
            System.out.println("该Class元素上的@Anno注解为:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class)));
            // 获取该Class对象所对应类的全部内部类
            Class<?>[] inners = clazz.getDeclaredClasses();
            System.out.println("ClassTest的全部内部类如下:");
            for (Class c : inners) {
                System.out.println(c);
            }
            // 使用Class.forName方法加载ClassTest的Inner内部类
            Class inClazz = Class.forName("ClassTest$Inner");
            // 通过getDeclaringClass()访问该类所在的外部类
            System.out.println("inClazz对应类的外部类为:" + inClazz.getDeclaringClass());
            System.out.println("ClassTest的包为:" + clazz.getPackage());
            System.out.println("ClassTest的父类为:" + clazz.getSuperclass());
        }
    }

    上面程序无须过多解释,程序获取了 ClassTest 类对应的 Class 对象后,通过调用该 Class 对象的不同方法来得到该 Class 对象的详细信息。运行该程序,会看到如下图所示的运行结果。

    ClassTest的全部构造器如下:
    private com.jwen.chapter18_3.ClassTest()
    public com.jwen.chapter18_3.ClassTest(java.lang.String)
    ClassTest的全部public构造器如下:
    public com.jwen.chapter18_3.ClassTest(java.lang.String)
    ClassTest的全部public方法如下:
    public static void com.jwen.chapter18_3.ClassTest.main(java.lang.String[]) throws java.lang.Exception
    public void com.jwen.chapter18_3.ClassTest.info(java.lang.String)
    public void com.jwen.chapter18_3.ClassTest.info()
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public java.lang.String java.lang.Object.toString()
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    ClassTest里带一个字符串参数的info()方法为:public void com.jwen.chapter18_3.ClassTest.info(java.lang.String)
    ClassTest的全部Annotation如下:
    @java.lang.Deprecated()
    @com.jwen.chapter18_3.Annos(value=[@com.jwen.chapter18_3.Anno(), @com.jwen.chapter18_3.Anno()])
    该Class元素上的@SuppressWarnings注解为:[]
    该Class元素上的@Anno注解为:[@com.jwen.chapter18_3.Anno(), @com.jwen.chapter18_3.Anno()]
    ClassTest的全部内部类如下:
    class com.jwen.chapter18_3.ClassTest$Inner
    inClazz对应类的外部类为:class com.jwen.chapter18_3.ClassTest
    ClassTest的包为:package com.jwen.chapter18_3
    ClassTest的父类为:class java.lang.Object

    从上图所示的运行结果来看,Class 提供的功能非常丰富,它可以获取该类里包含的构造器、方法、内部类、注解等信息,也可以获取该类所包括的成员变量(Field)信息——通过 getFields() 或 getField(String name) 方法即可。

    值得指出的是,虽然定义 ClassTest 类时使用了 @SuppressWarnings 注解,但程序运行时无法分析出该类里包含的该注解,这是因为 @SuppressWarnings 使用了 @Retention(value=SOURCE) 修饰,这表明@suppresswarnings 只能保存在源代码级别上,而通过 ClassTest.class 获取该类的运行时 Class 对象,所以程序无法访问到 @suppresswarnings 注解。

    注意:对于只能在源代码上保留的注解,使用运行时获得的 Class 对象无法访问到该注解对象。

    通过 Class 对象可以得到大量的 Method、Constructor、Field 等对象,这些对象分别代表该类所包括的方法、构造器和成员变量等,程序还可以通过这些对象来执行实际的功能,例如调用方法、创建实例。

    Java8 新增的方法参数反射

    Java8 在 java.lang.reflect 包下新增了一个 Executable 抽象基类,该对象代表可执行的类成员,该类派生了 Constructor、Method 两个子类。

    Executable 基类提供了大量方法来获取修饰该方法或构造器的注解信息:还提供了 isVarArgs() 方法用于判断该方法或构造器是否包含数量可变的形参,以及通过 getModifiers() 方法来获取该方法或构造器的修饰符。除此之外,Executable 提供了如下两个方法来获取该方法或参数的形参个数及形参名。

    • int getParameterCount():获取该构造器或方法的形参个数。
    • Parameter[] getParameters():获取该构造器或方法的所有形参。

    上面第二个方法返回了一个 Parameter[] 数组,Parameter 也是 Java8 新增的 API,每个 parameter 对象代表方法或构造器的一个参数。Parameter 也提供了大量方法来获取声明该参数的泛型信息,还提供了如下常用方法来获取参数信息。

    • getModifiers():获取修饰该形参的修饰符。
    • String getName():获取形参名。
    • Type getParameterizedType():获取带泛型的形参类型。
    • Class<?> getType():获取形参类型。
    • boolean isNamePresent():该方法返回该类的 class 文件中是否包含了方法的形参名信息。
    • boolean isVarArgs():该方法用于判断该参数是否为个数可变的形参。

    需要指出的是,使用 javac 命令编译 Java 源文件时,默认生成的 class 文件并不包含方法的形参名信息,因此调用 isNamePresent() 方法将会返回false,调用 getName() 方法也不能得到该参数的形参名。如果希望 javac 命令编译 Java 文件时可以保留形参信息,则需要为该命令指定 -parameters 选项。

    如下程序示范了 Java8 的方法参数反射功能。

    class Test {
        public void replace(String str, List<String> list) {
        }
    }
    
    public class MethodParameterTest {
        public static void main(String[] args) throws Exception {
            // 获取String的类
            Class<Test> clazz = Test.class;
            // 获取String类的带两个参数的replace()方法
            Method replace = clazz.getMethod("replace", String.class, List.class);
            // 获取指定方法的参数个数
            System.out.println("replace方法参数个数:" + replace.getParameterCount());
            // 获取replace的所有参数信息
            Parameter[] parameters = replace.getParameters();
            int index = 1;
            // 遍历所有参数
            for (Parameter p : parameters) {
    //            if (p.isNamePresent()) {
                    System.out.println("---第" + index++ + "个参数信息---");
                    System.out.println("参数名:" + p.getName());
                    System.out.println("形参类型:" + p.getType());
                    System.out.println("泛型类型:" + p.getParameterizedType());
    //            }
            }
        }
    }

     上面程序先定义了一个包含简单的 Test 类,该类中包含一个 replace(String str, List<String> list) 方法,程序中第一行粗体字代码获取了该方法,接下来程序中三行粗体字代码分别用于获取该方法的形参名、形参类型和泛型信息。

    由于上面程序中三行粗体字代码位于 p.isNamePresent() 条件为 true 的执行体内,也就是只有当该类的 class 文件中包含形参名信息时,程序才会执行条件体内的三行粗体字代码。因此需要使用如下命令来编译该程序:

    javac -parameter -d . MethodParameterTest.java

    上面命令中 -parameter 选项用于控制 javac 命令保留方法形参名信息。

    运行该程序,即可看到如下输出:

    replace方法参数个数:2
    ---第1个参数信息---
    参数名:arg0
    形参类型:class java.lang.String
    泛型类型:class java.lang.String
    ---第2个参数信息---
    参数名:arg1
    形参类型:interface java.util.List
    泛型类型:java.util.List<java.lang.String>
  • 相关阅读:
    安装XMind如何安装到指定目录
    显示器AVG、DVI、HDMI、DisplayPort、Type-C、雷电接口
    SATA与PCI-E速度对比
    SRAM、DRAM、Flash、DDR有什么区别
    USB3.0与Type-C接口的关系
    一图明白ACHI,SATA之间的关系
    U.2与M.2接口
    遗传算法实例分析
    从零开始写代码-python解深度学习神经网络题目
    基于Yen算法的k最短路径问题的python实现
  • 原文地址:https://www.cnblogs.com/jwen1994/p/12569017.html
Copyright © 2011-2022 走看看