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

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

获得Class对象

每个类被加载后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类,获得Class对象有三种方式:

  • 使用Class类的forName(String className)静态方法,该方法需要字符串参数,参数值是该类的全限定类名
  • 调用类的class属性来获取该类对应的Class对象
  • 调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所有Java对象都可以调用该方法,返回该对象所属类对应的Class对象

大部分时候应该采用第二种方法,代码更安全并且性能更好,获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象和该类的真实信息了

获取Class中的信息

Class类提供了大量的实例方法来获取该Class对象所对应类的详细信息:

获取Class对应类所包含的构造器

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

获取Class对应类所包含的方法

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

访问Class对应类所包含的成员变量

- Field getField(String name):返回此Class对象对应类的指定名称的public成员变量
- Field[] getFields():返回此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<?>的参数,用于获取指定的方法或指定的构造器,这个参数的作用主要是为了处理类里定义的方法有重载的情形,例如某个类包含多个相同的方法签名:

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

三个方法属于重载,方法名相同但是参数不同,经过参数判断具体调用,也就是说确定一个方法应该由方法名和形参列表共同确定唯一,但是形参名没有任何实际意义,只能由形参类型来确定

// 获取第二个infor方法
// 前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表
class.getMethod("info", String.class)
// 获取第三个info方法
// 前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表
class.getMethod("info", String.class, Integer.class)

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

import java.util.*;
import java.lang.reflect.*;
import java.lang.annotation.*;

// 定义可重复注解
@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 (var c : ctors)
		{
			System.out.println(c);
		}
		// 获取该Class对象所对应类的全部public构造器
		Constructor[] publicCtors = clazz.getConstructors();
		System.out.println("ClassTest的全部public构造器如下:");
		for (var c : publicCtors)
		{
			System.out.println(c);
		}
		// 获取该Class对象所对应类的全部public方法
		Method[] mtds = clazz.getMethods();
		System.out.println("ClassTest的全部public方法如下:");
		for (var 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 (var 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 (var 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());
	}
}

Class提供的功能很多,可以获取该类里所包含的构造器、方法、内部类、注解等信息,也可以获取该类所包括的成员变量信息

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

方法参数反射

在Java8的java.lang.reflect包里新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor、Method两个子类
Executable基类提供了大量方法来获取修饰该方法或构造器的注解信息,还提供了isVarArgs()方法用于判断该方法或构造器是否包含数量可变的形参,以及通过getModifiers()方法来获取该方法或构造器的修饰符,此外Executable提供了如下两个方法来获取该方法或参数的形参个数及形参名

  • int getParameterCount(): 获取该构造器或方法的形参个数
  • Parameter[] getParameters(): 获取该构造器或方法的所有形参,返回一个Parameters[]数组,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选项

import java.lang.reflect.*;
import java.util.*;

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();
		var index = 1;
		// 遍历所有参数
		for (var p : parameters)
		{
			if (p.isNamePresent())
			{
				System.out.println("---第" + index++ + "个参数信息---");
				System.out.println("参数名:" + p.getName());
				System.out.println("形参类型:" + p.getType());
				System.out.println("泛型类型:" + p.getParameterizedType());
			}
		}
	}
}

最后的三个println代码中只有p.isNamePresent()条件为true的时候才会执行到,也就是只有当该类的class文件中包含形参名信息时,才会执行到最后的的println,因此在编译该源码文件的时候需要使用
javac -parameters -d .MethodParameterTest.java加了-parameters选项,用于控制javac命令保留方法形参名信息
执行结果为


D:\BaiduNetdiskDownload\CrazyJava\codes\18\18.3>javac -parameters MethodParameterTest.java

D:\BaiduNetdiskDownload\CrazyJava\codes\18\18.3>java MethodParameterTest
replace方法参数个数:2
---第1个参数信息---
参数名:str
形参类型:class java.lang.String
泛型类型:class java.lang.String
---第2个参数信息---
参数名:list
形参类型:interface java.util.List
泛型类型:java.util.List<java.lang.String>

D:\BaiduNetdiskDownload\CrazyJava\codes\18\18.3>