JavaSE进阶篇【5】——2022年6月30日

本文深入探讨了Java的反射机制,包括如何通过Class类获取类信息、访问构造方法、成员变量和方法,以及如何使用反射创建对象并调用方法。此外,还介绍了Java的注解功能,讲解了如何定义和访问Annotation信息,以及它们在程序中的作用和应用。
摘要由CSDN通过智能技术生成

JavaSE进阶篇【5】

2022年6月30日


第五部分 反射

5.1 Class 类与 Java 反射

Java 的反射 (reflection) 机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。
通过 Java 反射机制,可以在程序中访问已经装载到 JVM 中的 Java 对象的描述,实现访问、检测和修改描述 Java 对象信息本身的功能。在 java.lang.reflection 包中提供了对该功能的支持。
所有 Java 类都继承了 Object 类,在 Object 中定义了一个 getClass() 方法,该方法返回一个类型为 Class 的对象。
通过反射可以访问的主要描述信息如下

组成部分访问方法返回值类型说明
包路径getPackage()Package 对象获取该类的存放路径
类名称getName()String 对象获得该类的名称
继承类getSuperclass()Class 对象获取该类的继承类
实现接口getInterfaces()Class 型数组获得该类实现的所有接口
构造方法getConstructors()Constructor 型数组获得所有权限为 public 的构造方法
方法getMethods()Method 型数组获得所有权限为 public 的方法
成员变量getFields()Field 型数组获得所有权限为 public 的成员变量
内部类getClasses()Class 型数组获得所有权限为 public 的内部类
内部类的声明类getDeclaringClass()Class 对象如果该类为内部类,则返回它的成员类,否则返回 null

1. 访问构造方法

在通过下列一组方法访问构造方法时,将返回 Constructor 类型的对象或数组。每个 Constructor 对象代表一个构造方法,利用 Constructor 对象可以操纵相应的构造方法。

  • getConstructors()
  • getConstructor(Class<?>…parameterTypes)
  • getDeclaredConstructors()
  • getDeclaredConstructor(Class<?>…parameterTypes)

如果是访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。
Constructor 类中提供的常用方法如下表

方法说明
isVarArgs()查看该构造方法是否允许带有可变数量的参数,如果允许则返回 true,否则返回 false
getParameterTypes()按照声明顺序以 Class 数组的形式获得该构造方法的各个参数类型
getExceptionTypes()以 Class 数组的形式获得该构造方法可能抛出的异常类型
newInstance(Object…initargs)通过该构造方法利用指定的参数创建一个该类的对象,如果设置参数则表示采用默认无参数的构造方法
setAccessible(boolean flag)如果该构造方法的权限为 private,默认为不允许通过反射利用 newInstance(Object…initargs) 方法创建对象。如果先执行该方法,并将入口参数设为 true,则允许创建
getModifiers()获得可以解析出该构造方法所采用修饰符的整数

通过 java.lang.reflect.Modifier 类可以解析出 getModifiers() 方法的返回值所代表的修饰符信息,在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以以字符串的形式获得所有修饰符。该类常用的静态方法如下表所示

静态方法说明
isPublic(int mod)查看是否被 public 修饰符修饰,如果是则返回 true,否则返回 false
isProtected(int mod)查看是否被 protected 修饰符修饰,如果是则返回 true,否则返回 false
isPrivate(int mod)查看是否被 private 修饰符修饰,如果是则返回 true,否则返回 false
isStatic(int mod)查看是否被 static 修饰符修饰,如果是则返回 true,否则返回 false
isFinal(int mod)查看是否被 final 修饰符修饰,如果是则返回 true,否则返回 false
toString(int mod)以字符串的形式返回所有修饰符

来看一个访问构造放的例子,首先创建一个 Example_01 类,在该类中声明一个 String 类型成员变量和 3 个 int 型成员变量,并提供 3 个构造方法。

package part5_reflect;
import java.lang.*;
public class Example_01 {
	String s;
	int i,i2,i3;
	private Example_01() {
	}
		protected Example_01(String s, int i) {
			this.s = s;
			this.i = i;
		}
	public Example_01(String...strings) throws NumberFormatException{
		if(0<strings.length)
			i = Integer.valueOf(strings[0]);
		if(1<strings.length)
			i2 = Integer.valueOf(strings[1]);
		if(2<strings.length)
			i3 = Integer.valueOf(strings[2]);
	}
	public void print() {
		System.out.println("s="+s);
		System.out.println("i="+i);
		System.out.println("i2="+i2);
		System.out.println("i3="+i3);
	}
}

然后编写测试类 Main_01,在该类中通过反射访问 Example_01 类中的所有构造方法,并将该构造方法是否允许带有可变数量的参数、入口参数类型和可能抛出的异常类型信息输出到控制台。

package part5_reflect;
import java.lang.*;
import java.lang.reflect.Constructor;
public class main_01 {
	public static void main(String[] args) {
		Example_01 example = new Example_01("10","20","30");
		Class<?extends Example_01>exampleC = example.getClass();
		//获得所有构造方法
		Constructor[] declaredConstructors = exampleC.getDeclaredConstructors();
		for(int i=0;i<declaredConstructors.length;i++) {        //构造遍历方法
			Constructor<?>constructor = declaredConstructors[i];
			System.out.println("查看是否允许带可变数量的参数:"+constructor.isVarArgs());
			System.out.println("该构造方法的入口参数类型以此为:");
			Class[] parameterTypes = constructor.getParameterTypes(); //获取所有参数类型
			for(int j=0;j<parameterTypes.length;j++) {
				System.out.println(" "+parameterTypes[j]);
			}
		    System.out.println("该构造方法可能抛出的异常类型为:");
			//获取所有可能抛出异常信息类型
		    Class[] exceptionTypes = constructor.getExceptionTypes();
			for(int j=0;j<exceptionTypes.length;j++) {
				System.out.println(" "+exceptionTypes[j]);
			}
			Example_01 example2 = null;
			while(example2==null) {
				try {
					if(i==2)
						example2=(Example_01)constructor.newInstance();
					else if(i==1)
						example2=(Example_01)constructor.newInstance("7",5);
					else {
						Object[] parameters = new Object[] {new String[] {"100","200","300"}};
						example2=(Example_01)constructor.newInstance(parameters);
					}
				}catch(Exception e) {
					System.out.println("在创建对象时抛出异常,下面执行 setAccessible() 方法");
					constructor.setAccessible(true);
				}
			}
			if(example2!=null) {
				example2.print();
				System.out.println();
			}
		}
	}
}

控制台输出的结果为
在这里插入图片描述

2. 访问成员变量

在通过下列一组方法访问成员变量时,将返回 Field 类型的对象或数组。每个 Field 对象代表一个成员变量,利用 Field 对象可以操纵相应的成员变量。

  • getFields()
  • getField(String naem)
  • getDeclaredFields()
  • getDeclaredField(String name)

如果是访问指定的成员变量,可以通过该成员变量的名称来访问。
Field 类中提供的常用方法如下表所示

方法说明
getName()获得该成员变量的名称
getType()获得表示该成员变量类型的 Class 对象
get(Object obj)获得指定对象 obj 中成员变量的值,返回值为 Object 型
set(Object obj, Object value)将指定对象 obj 中成员变量的值设置为 value
getInt(Object obj)获得指定对象 obj 中类型为 int 的成员变量的值
setInt(Object obj, int i)将指定对象 obj 中类型为 int 的成员变量的值设置为 i
setAccesible(boolean flag)此方法可以设置是否忽略权限限制直接访问 private 等私有权限的成员变量
getModifiers()获得可以解析出该成员变量所采用修饰符的整数

3. 访问方法

在通过下列一组方法访问方法时,将返回 Method 类型的对象或数组。每个 Method 对象代表一个方法,利用 Method 对象可以操纵相应的方法。

  • getMethods()
  • getMethod(String name,Class<?>…parameterTypes)
  • getDeclaredMethods()
  • getDeclaredMethod(String name,Class<?>parameterTypes)

如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问,例如,访问一个名称为 print、入口参数类型依次为 String 和 int 型的方法,通过下面两种方式均可实现

  • objectClass.getDeclaredMethod(“print”, String.class, int.class)
  • objectClass.getDeclaredMethod(“print”,new Class[]{String.class, int.class})

Method 类中提供的常用方法如下表所示

方法说明
getName()获得该方法的名称
getParameterTypes()按照声明顺序以 Class 数组的形式获得该方法的各个参数的类型
getReturnType()以 Class 对象的形式获得该方法的返回值的类型
getExceptionTypes()以 Class 数组的形式获得该方法可能抛出的异常类型
invoke(Object obj, Object…args)利用指定参数 args 执行指定对象 obj 中的该方法,返回值为 Object 型
isVarArgs()查看该构造方法是否允许带有可变数量的参数,如果允许则返回 true,否则返回 false
getModifiers()获得可以解析出该方法所采用修饰符的整数
5.2 使用 Annotation 功能

Java 中提供了 Annotation 功能,该功能可用于类、构造方法、成员变量、方法、参数等的声明中。该功能并不影响程序的运行,但是会对编译器警告等辅助工具产生影响。下面介绍 Annotation 功能的使用方法,

1. 定义 Annotation 类型

在定义 Annotation 类型时,也需要用到用来定义接口的 interface 关键字,但需要在 interface 关键字前加一个 “@” 符号,即定义 Annotation 类型的关键字为 @interface,这个关键字的隐含意思是继承了 java.lang.annotation.Annotation 接口。例如,下面的代码就定义了一个 Annotation 类型:

public @interface NoMemberAnnotation{
}

上面定义的 Annotation 类型 @NoMemberAnnotation 未包含任何成员,这样的 Annotation 类型被称为 mark annotation。下面代码定义了一个只包含一个成员的 Annotation 类型:

public @interface OneMemberAnnotation{
    String value();
}
  • String:成员类型。可用的成员类型有 String、Class、primitive、enumerated 和 annotation,以及所列类型的数组
  • value:成员名称。如果在所定义的 Annotation 类型中只包含一个成员,通常将成员名称命名未 value

下面代码定义了一个包含多个的 Annotation 类型:

public @interface MoreMemberAnnotation{
    String descibe();
    Class type();
}

在为 Annotation 类型定义成员时,也可以为成员设置默认值,例如:

public @interface DefaultValueAnnotation{
    String describe() default "<默认值>";
    Class type() default void.class;
}

在定义 Annotation 类型时,还可以通过 Annotation 类型 @Target 来设置 Annotation 类型适用的程序元素种类。如果未设置 @Target,则表示适用于所有程序元素。枚举类 ElementType 中的枚举常用来设置 @Targer,如下表所示:

枚举常量说明
ANNOTATION_TYPE表示用于 Annotation 类型
TYPE表示用于类、接口和枚举,以及 Annotation 类型
CNSTRUCTOR表示用于构造方法
FIELD表示用于成员变量和枚举变量
METHOD表示用于方法
PARAMETER表示用于参数
LOCAL_VARIABLE表示用于局部变量
PACKAGE表示用于包

通过 Annotation 类型 @Retention 可用设置 Annotation 的有效范围。枚举类 RetentionPolicy 中的枚举常量用来设置 @Retention,如下表所示。如果没有设置 @Retention,Annotation 的有效范围为枚举常量 CLASS 表示的范围。

枚举常量说明
SOURCE表示不编译 Annotation 到类文件中,有效范围最小
CLASS表示编译 Annotation 到类文件中,但是在运行时不加载 Annotation 到 JVM 中
RUNTIME表示在运行时加载 Annotation 到 JVM 中,有效范围最大

2. 访问 Annotation 信息

如果在定义 Annotation 类型时将 @Retention 设置为 RetentionPolicy.RUNTIME,那么在运行程序时通过反射就可以获取到相关的 Annotation 信息,如获取构造方法、字段和方法的 Annotation 信息。
类 Constructor、Field 和 Method 均继承了 AccessibleObject 类,在 AccessibleObject 类中定义了 3 个关于 Annotation 的方法:

  • isAnnotationPresent(Class<?extends Annotation>annotationClass) 方法:用来查看是否添加了指定类型的 Annotation,如果是则返回 true,否则返回 false
  • getAnnotation(ClassannotationClass) 方法:用来获得指定类型的 Annotation,如果存在则返回相应的对象,否则返回 null
  • getAnnotations() 方法:用来获取所有的 Annotation,该方法将返回一个 Annotation 数组

在类 Constructor 和 Method 中还定义了方法 getParameterAnnotation(),用来获得所有参数添加的 Annotation,将以 Annotation 类型的二维数组返回,在数组中的顺序与声明的顺序相同,如果没有参数则返回一个长度为 0 的数组;如果存在未添加 Annotation 的参数,将用一个长度为 0 的嵌套数组占位。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值