0.什么是反射?
反射是 Java 中一个非常强大的机制。
当我们需要编写一个 Java 程序时,我们需要知道并且引入我们需要使用的类,随后使用该类方法。
这种方式存在一个限制,即我们必须在编译时期就明确知道需要使用哪些类,并且必须在编译期将这些类的信息编码在程序中。
但是,在某些情况下,我们希望能够在运行时期动态获取并使用类,而不是事先硬编码在程序中。这时候,Java 反射机制就发挥作用了。
反射机制是指在运行时期获取和操作类的信息的能力,通过反射我们可以在程序运行时动态获取类的信息、动态创建对象、动态调用方法。
但需要注意的是,由于反射机制的灵活性,它可能导致代码的可读性和性能出现问题。
获取与更改私有字段
在 0 中我们已经讲解了反射的概念,接下来让我们简单的试试反射,获取一个类中的私有字段。
1.让我们先新建一个类,并添加私有字段 name,如下:
接下来让我们使用反射获取 Example 类中的私有字段 name。
首先我们需要获取到 Example 类的 Class 对象,我们可以通过 Class.forName() 进行获取。
随后我们需要获取字段,使用 Class. getDeclaredField() 方法获取指定名称的字段,因为 name 字段是私有的,所以需要使用 getDeclaredField 方法。
也许你可能会比较疑惑,为什么要用 getDeclaredField?
因为使用 getDeclaredField() 方法可以获取到类的所有字段,包括私有字段和公有字段。如果是公有字段,则可以使用 getField() 方法来获取。
那么这两个方法有什么不同? 其中getField() 只能获取类中的公有字段,如果要获取私有字段则会抛出NoSuchFieldException 异常。getDeclaredField() 可以获取类中的所有字段,包括公有字段和私有字段。
总的来说,如果要获取的字段是公有的,可以使用 getField() 方法,如果要获取的字段是私有的,就需要使用 getDeclaredField() 方法来获取。
接下来,由于 name 字段是私有的,需要通过 Field.setAccessible(true) 方法来设置访问权限为可访问,才能够获取和修改该字段的值。
那么,再讲一下什么是 setAccessible() 方法,它是 Java 反射 API 中的一个方法,允许我们访问以及修改那些修饰符不允许的成员。
一般情况下,反射只能获取 public 成员,但是很显然我们肯定不想只局限于获取 public 成员,这时候我们就需要使用 setAccessible() 方法来解决这个问题。
接下来我们需要创建一个 Example 对象,随后进行获取,由于我们并没有给 Example 写构造器,如果我们没有给类写构造器,那么编译器会默认生成无参构造器。
也就是说我们需要进行无参构造。
在 JDK9 之后,Class.newInstance() 方法已经被废弃了,因为它不够灵活。现在应该使用 Class.getDeclaredConstructor().newInstance(),其中 getDeclaredConstructor() 可以传入构造方法的参数类型,或者不传入来获取默认的无参构造方法。
在上文我们已经写了 nameField 是 Example 类的私有字段 name,obj 则是创建的 Example 对象,接下来我们进行获取即可。我们使用 Field.get() 进行获取:
输出:
完整代码如下:
这看上去非常简单! 运行后输出MorePokemon,这是我们想要的结果!
接下来我们想要更改这个 name,我们只需要使用 nameField.set() 方法即可!
输出为: MorePixelmon 这是我们想要的结果!
也许你可能会疑惑,Field 到底是什么东西? 接下来让我们简单的讲讲 Field。
Field 在 Java 中是一个反射类,表示一个类中的成员变量,使用 Field 对象可以获取和修改指定的成员变量,既使用 set get 等。
我们使用了 nameField.set(obj, "二百!"); 来进行设置,obj 实际上表示为被访问或修改的对象,通过 set 来设置 obj 指定的 Field 成员变量,get 来获取。
2.调用有参构造器
我们已经在 1 中学会了要怎么使用反射来访问成员变量,那么接下来让我们来调用有参构造器,让我们拓展 Example 类,为其添加一个构造器:
我们已经为 Example 类添加了一个构造器,需要传入一个字符串类参数作为 name,接下来让我们使用反射调用它。
我们在 1 中已经知道了要如何调用无参构造器,这与调用有参构造器相差不大,只是在于参数传递不同。
我们在 Example 的构造器中需要传入一个 String 类型的参数:
那么我们需要这样更改 obj 的创建:
在 getDeclaredConstructor 中我们需要规定传入的参数的类型,随后在 newInstance 中传入实际参数即可。
最后的输出为: MorePixelmon 这是我们想要的!
那么多参数应该如何传递? 接下来让我们再拓展一下 Example 类,为其添加一个新的字段:
这实际上很简单,我们只需要继续传入即可:
接下来让我们再创建一个 Field 对象,为 age 字段,并且获取 obj 中的 age 字段输出。
输出为:
MorePixelmon
1
这是我们想要的
3.调用私有方法
在 1 和 2 中我们已经知道了如何访问私有字段以及更改私有字段、调用无参或有参构造器,那么接下来让我们学学如何调用私有方法。
还是一样,让我们先拓展 Example 类,让我们为其写两个方法,用来设置 name 以及 age:
那么接下来,让我们接触一个新的类 – Method。
Method 代表了类的方法,每个方法都是一个 Method 对象,它包含方法名称、返回类型、参数、修饰符等等信息,我们可以使用 invoke() 方法调用一个对象的方法。
我们可以使用 Class.getMethod() 来获得 public 类型 Method 对象,如果是 private,那么和 getDeclaredField() 一样,我们使用 getDeclaredMethod() 即可,由于 setName() 方法是私有的,那么我们使用 getDeclaredMethod()。
getDeclaredMethod() 方法需要传入对象的方法名称,以及参数类型:
这段代码我们创建了 setNameMethod 对象,使用 getDeclaredMethod() 获取私有方法,由于 setName() 需要传入一个 String 类型参数,所以此处进行规定。
另外别忘了,由于 setName() 方法是私有,所以我们还需要用到 setAccessible(true) 更改访问权限,否则会抛出 IllegalAccessException 异常。
完整代码:
输出为:
MorePokemon
1
可以看到名字已经被正确的更改了,那么我们使用相同的方法来更改一下 age:
输出为:
MorePixelmon
114
这是我们想要的!
4.反射获取注解
在 1 2 3 中我们已经基本入门简单的反射了,那么接下来我们来学学要如何通过反射来获取注解,老样子,让我们拓展 Example 类,并且写一个自定义注解类:
这是一个自定义注解类,此注解没有实际作用,只是测试用:
首先我们需要意识到一点,注解是有保留策略的,其中保留策略既为 RetentionPolicy 注解规定,保留策略有三种:
1.SOURCE - 注释将被编译器丢弃
2.CLASS - 注释由编译器记录在类文件中,但不需要由 VM 在运行时保留。 这是默认
3.RUNTIME - 注释由编译器记录在类文件中,并在运行时由 VM 保留,因此它们可以被反射读取
其中只有 RUNTIME 保留策略的注解可以被反射读取,因为只有 RUNTIME 在运行时由 VM 进行保留,SOURCE 保留策略注解将在编译时被丢弃,而 CLASS 会将注解信息编译到生成的 .class 文件中,但在运行时期间不会将注解信息加载到内存中。
这就是为什么有的注解无法通过反射获取,比如 @SuppressWarnings。
@SuppressWarnings 是一个编译时注解,它的保留策略是 RetentionPolicy.SOURCE,意味着在编译后它会被丢弃,因此在运行时使用反射时无法获取它。
随后让我们给字段以及方法使用 MyAnnotation 注解
我们只需要使用 getAnnotation() 方法获取注解即可,Field、Method、Class 都具有此方法,让我们先获取 name 以及 age 字段的注解:
我们只要创建一个新的注解对象,并且使用 getAnnotation() 方法即可,getAnnotation() 方法内填入注解类。
需要注意的是,如果对应方法没有注解,那么此注解对象为空,在使用前需要进行空判断,让我们进行输出:
我们这里获取了 nameField 以及 ageField 的 MyAnnotaion 注解值,输出为:
1
2
这是我们想要的!
name 与 age 的注解值确实分别为 1 2,是正确的!
接下来让我们获取方法的注解值:
进行输出:
输出为: 4
这是我们想要的!
5.反射获取注解
在上面的 1 2 3 4 教程中都是非静态字段与方法,那么接下来我们应该如何访问静态字段与方法,这很简单! 让我们新建一个静态类 StaticExample:
其中有两个静态私有字段,为 name 与 age,一个静态私有方法 send,需要传入 String 类型参数。
我们只需要让 Object 为 null 调用即可:
输出为:
send!
---------------------
MP
114514
---------------------
MP
114
这是我们想要的!
6.反射获取父类
我们可以通过 Class. getSuperclass() 方法来获取父类,让我们先写几个示例类:
可以看到,Cat 类继承了 Animal,让我们通过反射获取 Cat 的父类:
输出为: Super class: Animals.Animal
这是我们想要的!
7.反射获取接口
我们可以通过 Class. getInterfaces () 方法来获取父类,让我们先写几个示例类:
可以看到,Cat 实现了 Animal 接口,让我们通过反射获取 Cat 类的接口:
输出为: Interface: Animals.Animal
这是我们想要的!
8.反射获取和操作类加载器
反射操作类加载器主要用于动态加载类、拓展程序、热部署、安全管理等等场景,可以使得程序拓展性与可维护性更强。
示例:
输出为: name: Hello World
以上代码通过反射获取当前线程的类加载器,用类加载器加载 Test 类,获取构造器并且创建实例,获取名称输出结果。
为什么用 loadClass 而不是直接 Class.fromName()?
这两种方法都是返回与指定类名对应的 Class 对象,但是它们之间存在一些差别:
Class.forName() 方法会触发类的初始化,而 ClassLoader.loadClass() 方法则不会。
在实际开发中,通常优先使用 ClassLoader.loadClass() 方法,因为它可以避免不必要的类初始化,提高程序的性能。
使用 ClassLoader.loadClass() 方法还可以实现更加灵活的类加载策略。可以通过自定义类加载器来实现自己的类加载逻辑,加载加密过的类、从远程服务器上加载类等。
9.反射获取和操作枚举类型
使用反射获取和操作枚举类型的方法与普通类类似,不过需要注意一些特殊的地方,以下是实例:
输出为:
TEST2
1
TEST2
这是我们想要的!
10.性能问题
反射提供了强大的灵活性,但是对性能有一定的影响,由于反射调用方法、访问字段等操作是在运行时动态解析的,所以它们不会被 JIT 编译器优化,导致反射操作比直接调用方法等操作更慢。
且可能会破坏封装性,因为它可以访问和修改私有方法、字段等成员。这会导致安全性问题,并增加代码的维护难度。
谨慎使用反射、避免在需要高性能的代码中频繁使用反射操作。
如果您看完了本教程,那么您应该已经算是入门反射了,恭喜!
反射是 Java 中一个非常强大的机制。
当我们需要编写一个 Java 程序时,我们需要知道并且引入我们需要使用的类,随后使用该类方法。
这种方式存在一个限制,即我们必须在编译时期就明确知道需要使用哪些类,并且必须在编译期将这些类的信息编码在程序中。
但是,在某些情况下,我们希望能够在运行时期动态获取并使用类,而不是事先硬编码在程序中。这时候,Java 反射机制就发挥作用了。
反射机制是指在运行时期获取和操作类的信息的能力,通过反射我们可以在程序运行时动态获取类的信息、动态创建对象、动态调用方法。
但需要注意的是,由于反射机制的灵活性,它可能导致代码的可读性和性能出现问题。
获取与更改私有字段
在 0 中我们已经讲解了反射的概念,接下来让我们简单的试试反射,获取一个类中的私有字段。
1.让我们先新建一个类,并添加私有字段 name,如下:
Makefile:
public class Example {
private String name = "MorePokemon";
}
接下来让我们使用反射获取 Example 类中的私有字段 name。
首先我们需要获取到 Example 类的 Class 对象,我们可以通过 Class.forName() 进行获取。
Java:
Class<?> clazz = Class.forName("Example");
随后我们需要获取字段,使用 Class. getDeclaredField() 方法获取指定名称的字段,因为 name 字段是私有的,所以需要使用 getDeclaredField 方法。
Java:
Field nameField = clazz.getDeclaredField("name");
因为使用 getDeclaredField() 方法可以获取到类的所有字段,包括私有字段和公有字段。如果是公有字段,则可以使用 getField() 方法来获取。
那么这两个方法有什么不同? 其中getField() 只能获取类中的公有字段,如果要获取私有字段则会抛出NoSuchFieldException 异常。getDeclaredField() 可以获取类中的所有字段,包括公有字段和私有字段。
总的来说,如果要获取的字段是公有的,可以使用 getField() 方法,如果要获取的字段是私有的,就需要使用 getDeclaredField() 方法来获取。
接下来,由于 name 字段是私有的,需要通过 Field.setAccessible(true) 方法来设置访问权限为可访问,才能够获取和修改该字段的值。
Java:
nameField.setAccessible(true);
那么,再讲一下什么是 setAccessible() 方法,它是 Java 反射 API 中的一个方法,允许我们访问以及修改那些修饰符不允许的成员。
一般情况下,反射只能获取 public 成员,但是很显然我们肯定不想只局限于获取 public 成员,这时候我们就需要使用 setAccessible() 方法来解决这个问题。
接下来我们需要创建一个 Example 对象,随后进行获取,由于我们并没有给 Example 写构造器,如果我们没有给类写构造器,那么编译器会默认生成无参构造器。
也就是说我们需要进行无参构造。
Java:
Object obj = clazz.getDeclaredConstructor().newInstance();
在 JDK9 之后,Class.newInstance() 方法已经被废弃了,因为它不够灵活。现在应该使用 Class.getDeclaredConstructor().newInstance(),其中 getDeclaredConstructor() 可以传入构造方法的参数类型,或者不传入来获取默认的无参构造方法。
在上文我们已经写了 nameField 是 Example 类的私有字段 name,obj 则是创建的 Example 对象,接下来我们进行获取即可。我们使用 Field.get() 进行获取:
Java:
nameField.get(obj)
输出:
Java:
System.out.println(nameField.get(obj));
完整代码如下:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Example");
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println(nameField.get(obj));
}
这看上去非常简单! 运行后输出MorePokemon,这是我们想要的结果!
接下来我们想要更改这个 name,我们只需要使用 nameField.set() 方法即可!
Java:
nameField.set(obj, "MorePixelmon");
System.out.println(nameField.get(obj));
输出为: MorePixelmon 这是我们想要的结果!
也许你可能会疑惑,Field 到底是什么东西? 接下来让我们简单的讲讲 Field。
Field 在 Java 中是一个反射类,表示一个类中的成员变量,使用 Field 对象可以获取和修改指定的成员变量,既使用 set get 等。
我们使用了 nameField.set(obj, "二百!"); 来进行设置,obj 实际上表示为被访问或修改的对象,通过 set 来设置 obj 指定的 Field 成员变量,get 来获取。
2.调用有参构造器
我们已经在 1 中学会了要怎么使用反射来访问成员变量,那么接下来让我们来调用有参构造器,让我们拓展 Example 类,为其添加一个构造器:
Java:
public class Example {
private String name = "MorePokemon";
public Example(String name) {
this.name = name;
}
}
我们已经为 Example 类添加了一个构造器,需要传入一个字符串类参数作为 name,接下来让我们使用反射调用它。
我们在 1 中已经知道了要如何调用无参构造器,这与调用有参构造器相差不大,只是在于参数传递不同。
我们在 Example 的构造器中需要传入一个 String 类型的参数:
Java:
public Example(String name) {
this.name = name;
}
那么我们需要这样更改 obj 的创建:
Java:
Object obj = clazz.getDeclaredConstructor(String.class).newInstance("MorePixelmon");
在 getDeclaredConstructor 中我们需要规定传入的参数的类型,随后在 newInstance 中传入实际参数即可。
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Example");
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
Object obj = clazz.getDeclaredConstructor(String.class).newInstance("MorePixelmon");
System.out.println(nameField.get(obj));
}
最后的输出为: MorePixelmon 这是我们想要的!
那么多参数应该如何传递? 接下来让我们再拓展一下 Example 类,为其添加一个新的字段:
Java:
ublic class Example {
private String name = "MorePokemon";
private int age = 114514;
public Example(String name, int age) {
this.name = name;
this.age = age;
}
}
这实际上很简单,我们只需要继续传入即可:
Java:
Object obj = clazz.getDeclaredConstructor(String.class, int.class).newInstance("MorePixelmon", 1);
接下来让我们再创建一个 Field 对象,为 age 字段,并且获取 obj 中的 age 字段输出。
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Example");
Field nameField = clazz.getDeclaredField("name");
Field ageField = clazz.getDeclaredField("age");
nameField.setAccessible(true);
ageField.setAccessible(true);
Object obj = clazz.getDeclaredConstructor(String.class, int.class).newInstance("MorePixelmon", 1);
System.out.println(nameField.get(obj));
System.out.println(ageField.get(obj));
}
输出为:
MorePixelmon
1
这是我们想要的
3.调用私有方法
在 1 和 2 中我们已经知道了如何访问私有字段以及更改私有字段、调用无参或有参构造器,那么接下来让我们学学如何调用私有方法。
还是一样,让我们先拓展 Example 类,让我们为其写两个方法,用来设置 name 以及 age:
Java:
public class Example {
private String name = "MorePokemon";
private int age = 114514;
public Example(String name, int age) {
this.name = name;
this.age = age;
}
private void setName(String name) {
this.name = name;
}
private void setAge(int age) {
this.age = age;
}
}
那么接下来,让我们接触一个新的类 – Method。
Method 代表了类的方法,每个方法都是一个 Method 对象,它包含方法名称、返回类型、参数、修饰符等等信息,我们可以使用 invoke() 方法调用一个对象的方法。
我们可以使用 Class.getMethod() 来获得 public 类型 Method 对象,如果是 private,那么和 getDeclaredField() 一样,我们使用 getDeclaredMethod() 即可,由于 setName() 方法是私有的,那么我们使用 getDeclaredMethod()。
getDeclaredMethod() 方法需要传入对象的方法名称,以及参数类型:
Java:
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
这段代码我们创建了 setNameMethod 对象,使用 getDeclaredMethod() 获取私有方法,由于 setName() 需要传入一个 String 类型参数,所以此处进行规定。
另外别忘了,由于 setName() 方法是私有,所以我们还需要用到 setAccessible(true) 更改访问权限,否则会抛出 IllegalAccessException 异常。
Java:
setNameMethod.setAccessible(true);
完整代码:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Example");
Field nameField = clazz.getDeclaredField("name");
Field ageField = clazz.getDeclaredField("age");
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
nameField.setAccessible(true);
ageField.setAccessible(true);
setNameMethod.setAccessible(true);
Object obj = clazz.getDeclaredConstructor(String.class, int.class).newInstance("MorePokemon", 1);
setNameMethod.invoke(obj, "MorePokemon");
System.out.println(nameField.get(obj));
System.out.println(ageField.get(obj));
}
输出为:
MorePokemon
1
可以看到名字已经被正确的更改了,那么我们使用相同的方法来更改一下 age:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Example");
Field nameField = clazz.getDeclaredField("name");
Field ageField = clazz.getDeclaredField("age");
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
Method setAgeMethod = clazz.getDeclaredMethod("setAge", int.class);
nameField.setAccessible(true);
ageField.setAccessible(true);
setNameMethod.setAccessible(true);
setAgeMethod.setAccessible(true);
Object obj = clazz.getDeclaredConstructor(String.class, int.class).newInstance("MorePixelmon", 1);
setNameMethod.invoke(obj, "MorePixelmon");
setAgeMethod.invoke(obj, 114);
System.out.println(nameField.get(obj));
System.out.println(ageField.get(obj));
}
输出为:
MorePixelmon
114
这是我们想要的!
4.反射获取注解
在 1 2 3 中我们已经基本入门简单的反射了,那么接下来我们来学学要如何通过反射来获取注解,老样子,让我们拓展 Example 类,并且写一个自定义注解类:
这是一个自定义注解类,此注解没有实际作用,只是测试用:
Java:
public class Annotation {
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
}
首先我们需要意识到一点,注解是有保留策略的,其中保留策略既为 RetentionPolicy 注解规定,保留策略有三种:
1.SOURCE - 注释将被编译器丢弃
2.CLASS - 注释由编译器记录在类文件中,但不需要由 VM 在运行时保留。 这是默认
3.RUNTIME - 注释由编译器记录在类文件中,并在运行时由 VM 保留,因此它们可以被反射读取
其中只有 RUNTIME 保留策略的注解可以被反射读取,因为只有 RUNTIME 在运行时由 VM 进行保留,SOURCE 保留策略注解将在编译时被丢弃,而 CLASS 会将注解信息编译到生成的 .class 文件中,但在运行时期间不会将注解信息加载到内存中。
这就是为什么有的注解无法通过反射获取,比如 @SuppressWarnings。
@SuppressWarnings 是一个编译时注解,它的保留策略是 RetentionPolicy.SOURCE,意味着在编译后它会被丢弃,因此在运行时使用反射时无法获取它。
随后让我们给字段以及方法使用 MyAnnotation 注解
Java:
public class Example {
@Annotation.MyAnnotation("1")
private String name = "MorePokemon";
@Annotation.MyAnnotation("2")
private int age = 114514;
@Annotation.MyAnnotation("3")
public Example(String name, int age) {
this.name = name;
this.age = age;
}
@Annotation.MyAnnotation("4")
private void setName(String name) {
this.name = name;
}
@Annotation.MyAnnotation("5")
private void setAge(int age) {
this.age = age;
}
}
我们只需要使用 getAnnotation() 方法获取注解即可,Field、Method、Class 都具有此方法,让我们先获取 name 以及 age 字段的注解:
Java:
Annotation.MyAnnotation nameFieldMyAnnotation = nameField.getAnnotation(Annotation.MyAnnotation.class);
Annotation.MyAnnotation ageFieldMyAnnotation = ageField.getAnnotation(Annotation.MyAnnotation.class);
我们只要创建一个新的注解对象,并且使用 getAnnotation() 方法即可,getAnnotation() 方法内填入注解类。
需要注意的是,如果对应方法没有注解,那么此注解对象为空,在使用前需要进行空判断,让我们进行输出:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Example");
Field nameField = clazz.getDeclaredField("name");
Field ageField = clazz.getDeclaredField("age");
nameField.setAccessible(true);
ageField.setAccessible(true);
Annotation.MyAnnotation nameFieldMyAnnotation = nameField.getAnnotation(Annotation.MyAnnotation.class);
Annotation.MyAnnotation ageFieldMyAnnotation = ageField.getAnnotation(Annotation.MyAnnotation.class);
if (nameFieldMyAnnotation != null) {
System.out.println(nameFieldMyAnnotation.value());
}
if (ageFieldMyAnnotation != null) {
System.out.println(ageFieldMyAnnotation.value());
}
}
我们这里获取了 nameField 以及 ageField 的 MyAnnotaion 注解值,输出为:
1
2
这是我们想要的!
Java:
@Annotation.MyAnnotation("1")
private String name = "MP";
@Annotation.MyAnnotation("2")
private int age = 114514;
name 与 age 的注解值确实分别为 1 2,是正确的!
接下来让我们获取方法的注解值:
Java:
Annotation.MyAnnotation setNameMethodMyAnnotation = setNameMethod.getAnnotation(Annotation.MyAnnotation.class);
进行输出:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Example");
Field nameField = clazz.getDeclaredField("name");
Field ageField = clazz.getDeclaredField("age");
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
nameField.setAccessible(true);
ageField.setAccessible(true);
setNameMethod.setAccessible(true);
Annotation.MyAnnotation setNameMethodMyAnnotation = setNameMethod.getAnnotation(Annotation.MyAnnotation.class);
if (setNameMethodMyAnnotation != null) {
System.out.println(setNameMethodMyAnnotation.value());
}
}
输出为: 4
Java:
@Annotation.MyAnnotation("4")
private void setName(String name) {
this.name = name;
}
这是我们想要的!
5.反射获取注解
在上面的 1 2 3 4 教程中都是非静态字段与方法,那么接下来我们应该如何访问静态字段与方法,这很简单! 让我们新建一个静态类 StaticExample:
Java:
public class StaticExample {
@Annotation.MyAnnotation("1")
private static String name = "MP";
@Annotation.MyAnnotation("2")
private static int age = 114514;
@Annotation.MyAnnotation("3")
private static void send(String string) {
System.out.println(string);
}
}
其中有两个静态私有字段,为 name 与 age,一个静态私有方法 send,需要传入 String 类型参数。
我们只需要让 Object 为 null 调用即可:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("StaticExample");
Field nameField = clazz.getDeclaredField("name");
Field ageField = clazz.getDeclaredField("age");
Method send = clazz.getDeclaredMethod("send", String.class);
nameField.setAccessible(true);
ageField.setAccessible(true);
send.setAccessible(true);
send.invoke(null, "send!");
System.out.println("---------------------");
System.out.println(nameField.get(null));
System.out.println(ageField.get(null));
System.out.println("---------------------");
nameField.set(null, "二百!!!!!");
ageField.set(null, 114);
System.out.println(nameField.get(null));
System.out.println(ageField.get(null));
输出为:
send!
---------------------
MP
114514
---------------------
MP
114
这是我们想要的!
6.反射获取父类
我们可以通过 Class. getSuperclass() 方法来获取父类,让我们先写几个示例类:
Java:
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + " eat");
}
public void sleep(){
System.out.println(name + " sleep");
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Java:
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
public void meow() {
System.out.println(super.getName() + " meow!");
}
}
可以看到,Cat 类继承了 Animal,让我们通过反射获取 Cat 的父类:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Animals.Cat");
Class<?> superClass = clazz.getSuperclass();
System.out.println("Super class: " + superClass.getName());
}
输出为: Super class: Animals.Animal
这是我们想要的!
7.反射获取接口
我们可以通过 Class. getInterfaces () 方法来获取父类,让我们先写几个示例类:
Java:
public interface Animal {
void eat();
void sleep();
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("eat");
}
@Override
public void sleep() {
System.out.println("sleep");
}
}
可以看到,Cat 实现了 Animal 接口,让我们通过反射获取 Cat 类的接口:
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Animals.Cat");
for (Class<?> interfaceClass : clazz.getInterfaces()) {
System.out.println("Interface: " + interfaceClass.getName());
}
}
输出为: Interface: Animals.Animal
这是我们想要的!
8.反射获取和操作类加载器
反射操作类加载器主要用于动态加载类、拓展程序、热部署、安全管理等等场景,可以使得程序拓展性与可维护性更强。
示例:
Java:
public class Test {
private String name;
public Test(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Java:
public static void main(String[] args) throws Exception {
// 获取当前线程的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 加载Test类
Class<?> clazz = classLoader.loadClass("com.example.Test");
// 获取Test类的构造器
Constructor<?> constructor = clazz.getConstructor(String.class);
// 创建Test类的实例
Object obj = constructor.newInstance("Hello World");
// 调用Test类的getName方法
String name = (String) clazz.getMethod("getName").invoke(obj);
System.out.println("name: " + name);
}
输出为: name: Hello World
以上代码通过反射获取当前线程的类加载器,用类加载器加载 Test 类,获取构造器并且创建实例,获取名称输出结果。
为什么用 loadClass 而不是直接 Class.fromName()?
这两种方法都是返回与指定类名对应的 Class 对象,但是它们之间存在一些差别:
Class.forName() 方法会触发类的初始化,而 ClassLoader.loadClass() 方法则不会。
在实际开发中,通常优先使用 ClassLoader.loadClass() 方法,因为它可以避免不必要的类初始化,提高程序的性能。
使用 ClassLoader.loadClass() 方法还可以实现更加灵活的类加载策略。可以通过自定义类加载器来实现自己的类加载逻辑,加载加密过的类、从远程服务器上加载类等。
9.反射获取和操作枚举类型
使用反射获取和操作枚举类型的方法与普通类类似,不过需要注意一些特殊的地方,以下是实例:
Java:
public enum EnumsTest {
TEST("TEST :D"),
TEST2("TEST2 :D");
private final String value;
EnumsTest(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
Java:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("EnumsTest");
Object[] enumConstants = clazz.getEnumConstants();
// 获取枚举常量的名称
String name = ((Enum<?>) enumConstants[1]).name();
// 获取枚举常量的序号
int ordinal = ((Enum<?>) enumConstants[1]).ordinal();
// 使用 getValue 获取属性值
String value = (String) clazz.getMethod("getValue").invoke(enumConstants[1]);
System.out.println(name);
System.out.println(ordinal);
System.out.println(value);
}
输出为:
TEST2
1
TEST2
这是我们想要的!
10.性能问题
反射提供了强大的灵活性,但是对性能有一定的影响,由于反射调用方法、访问字段等操作是在运行时动态解析的,所以它们不会被 JIT 编译器优化,导致反射操作比直接调用方法等操作更慢。
且可能会破坏封装性,因为它可以访问和修改私有方法、字段等成员。这会导致安全性问题,并增加代码的维护难度。
谨慎使用反射、避免在需要高性能的代码中频繁使用反射操作。
如果您看完了本教程,那么您应该已经算是入门反射了,恭喜!
打赏用户