Java通过反射查看类信息

1.* 通过反射查看类信息

Java程序中的许多对象在运行时都会出现两种类型:编译时类型和运行时类型,例如代码:“Person p = new Student()”,这行代码将会生成一个p变量,该变量的编译时类型为Person,运行时类型为Student;除此之外,还有更极端的情形,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是Object,但程序又需要调用该对象运行时类型的方法。

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

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

1.*.& 获得Class对象

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

  • 使用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对象的方法来获得该对象和该类的真实信息了。

1.*.& 从Class中获取信息

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

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

  • Connstructor<T> getConstructor(Class<?>... parameterTypes):返回此Class对象对应类的指定public构造器。
  • Constructor<?>[] getConstructors():返回此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:

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

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

  • <A extends Annotation> A getAnnotation(Class<A> annotationClass):试图获取该Class对象对应类上指定类型的Annotation;如果该类型的注释不存在,则返回null。
  • Annotation[] getAnnotations():返回该Class对象对应类上的所有Annotation。
  • Annotation[] getDeclaredAnnotations():返回直接修饰该Class对应类的所有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<?>的参数,用于获取指定的方法或指定的构造器。关于这个参数的作用,假设某个类内包含如下3个info方法签名:

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

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

点击查看代码
//前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表
clazz.getMethod("info", String.class)

如果需要获取第三个info方法,则使用如下代码:

点击查看代码
//前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表
clazz.getMethod("info", String.class, Integer.class)

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

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

查看代码

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

点击查看代码
ClassTest的全部构造器如下:
private com.hzhiping.viewobj.ClassTest()
public com.hzhiping.viewobj.ClassTest(java.lang.String)
ClassTest的全部public构造器如下:
public com.hzhiping.viewobj.ClassTest(java.lang.String)
ClassTest的全部public方法如下:
public static void com.hzhiping.viewobj.ClassTest.main(java.lang.String[]) throws java.lang.Exception
public void com.hzhiping.viewobj.ClassTest.info()
public void com.hzhiping.viewobj.ClassTest.info(java.lang.String)
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() 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.hzhiping.viewobj.ClassTest.info(java.lang.String)
ClassTest的全部Annotation如下:
@java.lang.Deprecated(forRemoval=false, since="")
该Class元素上的@SuppressWarnings注释为:null
ClassTest的全部内部类如下:
class com.hzhiping.viewobj.ClassTest$Inner
Exception in thread "main" java.lang.ClassNotFoundException: com.hzhiping.reflection.ClassTest$Inner
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
    at com.hzhiping.viewobj.ClassTest.main(ClassTest.java:67)

Process finished with exit code 1

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

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

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

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

原文地址:http://www.cnblogs.com/hzhiping/p/16908289.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性