文章目录
激♂情互射,不如来个反射
0. 前言
反射无处不在:我们通过光的反射可以看到镜子里的自己;我们的反射弧反射可以产生应激反应;我们发动“圣防护罩”可以将攻击反射给对面…
而在面向对象中,反射是一种可以在运行时通过对象获取类,或者直接获取类对象的动态获取对象信息,动态调用对象方法的机制。是动态语言的关键。
众所周知,我们常用的Web框架就大量运用了反射机制,例如Spring。学习反射,是为日后学习框架原理打下基础。
不仅仅是Java,反射在其他支持面向对象的语言中也适用,例如C#、Python等。
参考教程
腾讯云社区-深度解析反射机制:https://cloud.tencent.com/developer/article/1416743
腾讯云社区-浩说编程优秀参考文章:https://cloud.tencent.com/developer/article/1862884
优快云-注解原理优秀参考文章:https://blog.youkuaiyun.com/nanhuaibeian/article/details/121290597
1. 反射
1.1 反射机制
反射机制允许程序在运行时检查、访问和修改自身的结构和行为。简单来说,它使得程序能够“自我观察”并对自身进行操作。使用反射,你可以动态地获取类的信息,如属性、方法和构造函数,并且可以在运行时创建、调用和修改它们。这种灵活性使得你可以在不事先知道类的具体细节的情况下,对其进行操作和扩展。
反射机制的概念读起来有点拗口,由于平时工作,若不是专门从事架构设计的工作,直接使用的场景可能会略少,因此我们需要多加练习,在练习的过程中主键理解。能较为顺利读懂框架的源码,吸收框架的设计思想,是我们的后期目标之一。
1.2 反射获取类对象
类对象是描述类的对象,描述该类有什么属性、方法,即类本身的对象。
一个ClassLoader下,一种类,只会有一个类对象存在。通常一个JVM下,只会有一个ClassLoader。
新建测试用类
public class Animal {
public static final String TAG = "Animal";
static {
System.out.println("Animal static block");
System.out.println(TAG);
}
private String name;
public Animal() {
this.name = "Animal";
}
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println("Animal eat");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
上述测试类我添加了普通属性、方法也添加了一个静态属性、块,用来验证当我们获取类对象时,同时类属性也会实例化、静态块也会被执行,且仅执行一次。
获取类对象
public static void main(String[] args) {
// get Animal class object
// using try catch to avoid exception
try {
// 1. using Class.forName() method
Class<?> animalClass = Class.forName("class19.Animal");
// 2. using Class.class method
Class<Animal> animalClass1 = Animal.class;
// 3. using Object.getClass() method
Class<? extends Animal> aClass = new Animal().getClass();
System.out.println(animalClass==animalClass1);
System.out.println(animalClass==aClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
上述 Demo 提供了3种方式给我们获取类对象:通过Class.forName()方法、通过类名.class、通过对象获取类对象。同时,通过执行结果,我们可以观察到:类属性只初始化了一次,类对象只有1个。
1.3 反射创建对象
通过反射创建对象,可以通过类对象获取构造器对象,再通过构造器对象创建一个对象:
public static void main(String[] args) {
// test use reflect to create Animal object
try {
Class<Animal> animalClass = Animal.class;
Constructor<Animal> constructor = animalClass.getConstructor();
Animal animal = constructor.newInstance();
System.out.println(animal.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
也可以通过类对象newInstance()方法获取:
public class TestReflectAnimal {
public static void main(String[] args) {
// test use reflect to create Animal object
try {
Class<Animal> animalClass1 = Animal.class;
// create Animal object
Animal animal = animalClass1.newInstance();
System.out.println(animal.getName());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
思考
当我们学会了通过类对象创建对象后,不难发现,我们只需要知道类名且项目里存在该类,我们就可以通过反射获取这些类的对象。我们观察一下我们平时做的项目,通过通过配置文件就可以对很多功能进行修改,这就是基于配置的思想,而底层通常都是反射实现配置文件影响实际对象的。
1.4 反射访问属性
反射获取属性通常有两个方法:
- getDeclaredField :getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段
- getField :getField 只能获取public的,包括从父类继承来的字段
public static void main(String[] args) {
// using reflect to get or set attributes of Animal object
Class<Animal> animalClass = Animal.class;
try {
Animal animal = animalClass.getConstructor().newInstance();
Field nameField = animalClass.getDeclaredField("name");
System.out.println(aniaml.getName());
// 由于是 private
nameField.setAccessible(true);
nameField.set(animal, "Tom");
System.out.println(animal.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
1.5 反射调用方法
反射获取类对象后,可以通过getMethod方法调用方法:
public static void main(String[] args) {
// using reflect to get or set methods of Animal object
Class<Animal> animalClass = Animal.class;
try {
Animal animal = animalClass.getConstructor().newInstance();
animal.eat();
System.out.println(animal.getName());
// using invoke to call method
animalClass.getMethod("setName",String.class).invoke(animal, "Tom");
System.out.println(animal.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
2. 注解
注解是基于反射实现的,我们趁热打铁,把注解也撸一撸。
2.1 注解的概念
注解(Annotation)是一种元数据,它提供了对程序代码进行标记和补充信息的方式。注解可以应用于类、方法、字段等程序元素上,用于提供额外的描述和配置信息。
注解的原理是通过Java反射机制实现的。在编译阶段,注解会被编译器读取并处理,可以根据注解的定义来生成额外的代码或者进行其他特定的操作。在运行时,通过反射机制,我们可以获取到类、方法、字段等程序元素上的注解,并根据注解的信息来进行相应的处理。
2.2 元注解
元注解是用于注解其他注解的注解。Java中提供了一些内置的元注解,用于对自定义注解进行修饰和控制。
常见的元注解包括:
- @Retention:指定注解的保留策略,有三个取值:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME。
- @Target:指定注解的作用目标,可以是类、方法、字段等。
- @Documented:指定注解是否生成在JavaDoc中。
- @Inherited:指定注解是否具有继承性。
2.3 自定义注解
我们也可以自定义注解,用于对程序代码进行标记和补充信息。自定义注解需要使用@interface关键字来定义,注解中可以定义属性、方法等元素。
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value() default "";
int count() default 0;
}
测试使用
@MyAnnotation(value = "Hello", count = 5)
public class MyClass {
public void myMethod() {
System.out.println("My Method");
}
}
在上述代码中,我们定义了一个名为 MyAnnotation 的自定义注解。该注解具有两个属性:value 和 count,并且为它们设置了默认值。
然后,我们将 @MyAnnotation 注解应用到 MyClass 类上,并为注解的属性 value 设置为 “Hello”,count 设置为 5。
通过反射,我们可以获取 MyClass 类上的注解,并获取注解的属性值:
public static void main(String[] args) {
Class<MyClass> myClass = MyClass.class;
MyAnnotation annotation = myClass.getAnnotation(MyAnnotation.class);
String value = annotation.value();
int count = annotation.count();
System.out.println("Value: " + value);
System.out.println("Count: " + count);
}
3. 总结
本文介绍了反射机制以及如何使用反射来获取类对象、创建对象、访问属性和调用方法。反射是一种强大而灵活的机制,能够在运行时动态地操作类和对象。同时,本文还简单介绍了注解的概念、原理和自定义注解的方式。注解是一种标记和补充信息的机制,能够在编译阶段和运行时对程序进行特定处理。通过反射和注解的结合,我们可以实现更加灵活和可扩展的代码逻辑。反射是框架底层原理,注解是框架注解代替配置的灵活体现。但是实际学起来两者都是代码理解起来还好,但是概念略为拗口。由于篇幅关系,本文也暂未深入讲解更多的注解以及反射更贴近实际底层的模拟应用,我们应在日常工作学习中多留意,多阅读,多思考,渐渐拓展,慢慢消化。
期待未来的某一天,我能和各位大佬们一样,读源码就像读爽文,能立马理解,一目十行!