反射
官方解释:
反射(Reflection)是一种强大的机制,它允许程序在运行时检查和操作类、接口、字段和方法。通过反射,Java代码可以获取任意类的内部信息,如成员变量、构造方法、方法和注解等,并可以动态地创建对象、调用方法以及修改属性值。
反射的核心在于获取类的Class对象,这是Java反射的起点。一旦有了Class对象,就可以使用反射API来获取该类的所有方法、构造方法、字段等信息,也可以调用这些方法和构造方法来创建对象或执行特定的操作。
需要注意的是,反射虽然强大,但也应该谨慎使用。因为反射会破坏封装性,并可能导致性能下降。在不需要动态改变程序行为的情况下,应该优先使用普通的方法调用和对象操作。
总结来说,Java中的反射是一种在运行时检查和操作程序结构的技术,它提供了一种动态、灵活的方式来操作Java类、接口和它们的成员。
自己的理解;
反射这个机制,让我们操作一些逻辑变得简单,让我们的代码变得简洁,在我们后面学到的Spring框架中,控制反转这个概念,底层采用了大量的反射机制,因为反射这个机制可以让我们
允许程序在运行时检查类、接口、字段和方法的信息,并可以动态地创建对象、调用方法和修改属性。依赖注入框架通过反射获取类的元数据信息,然后根据配置或注解等信息,动态地创建对象并注入依赖。
其实在很多小伙伴看到这个反射这个机制的时候还是一脸懵逼,因为本来创建对象一句话,new一个对象就好了,现在我们却要编写繁琐的代码,还牵扯到.class文件,这个加载先后,然后就搞糊涂了,我下面也将介绍一下这些相关的东西
反射机制 、 JVM(java虚拟机) 和.class文件 他们之间的联系
-
.class文件是Java程序的重要组成部分。它包含了Java类的信息,这些信息是JVM执行程序时所需要的。当Java编译器将源代码(.java文件)编译成字节码时,就会生成.class文件。这些文件是平台无关的,使得Java程序可以在任何支持JVM的平台上运行。
-
接下来,JVM是运行Java程序的虚拟计算机。它负责加载.class文件,解释和执行其中的字节码。当Java程序运行时,JVM会读取.class文件,并将其加载到内存中,然后按照字节码的指令执行程序。
-
而反射是Java的一种特性,它允许程序在运行时检查类、接口、字段和方法的信息,并可以动态地创建对象、调用方法和修改属性。通过反射,我们可以获取到类的Class对象,进而可以获取到类的所有方法、构造方法、字段等元数据信息,甚至可以动态地创建对象并调用其方法。
这三者之间的联系主要体现在以下几个方面:
- .class文件与JVM:JVM通过加载.class文件来执行Java程序。每个.class文件对应一个Java类,JVM通过读取这些文件来获取类的元数据信息,并据此执行相应的字节码指令。
反射与JVM:反射机制的实现依赖于JVM的支持。当我们在程序中使用反射API来获取类的信息或动态地创建对象时,实际上是JVM在运行时动态地加载和解析.class文件,并提供相应的反射服务。 - .class文件与反射:.class文件存储了类的元数据信息,而反射机制正是通过读取这些元数据信息来实现其功能的。当我们使用反射API来获取类的信息时,实际上是读取了.class文件中的元数据。
Class类对象的获取
在jvm加载的时候,会创建一个class对象
获取class类对象的三种方式
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全限定类名)
// 根据类名获取class类对象
Class<User> cls = User.class;
// 根据对象获取class类对象
User user = new User();
Class<? extends User> userCls = user.getClass();
//根据全限定类名获取class对象
Class<?> userCls2 = Class.forName("reflect.User");
常用API方法
Class<?> cls = Class.forName("reflect.Person");
/**
* String getName() :获取class类对象的全限定类名(包括包名)
*/
System.out.println(cls.getName());
/**
* String getSimpleName() :获取class类对象的类名(不包括包名)
*/
System.out.println(cls.getSimpleName());
/**
* boolean isInterface() :判断class类对象是不是表示一个接口
*/
System.out.println(cls.isInterface());
/**
* Class<?>[] getInterfaces() :返回class对象数组,表示Class对象所引用的类所实现的所有接口
*/
Class<?>[] interfaces = cls.getInterfaces();
System.out.println(Arrays.toString(interfaces));
/**
* Object newInstance():返回一个Object对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
*/
Object obj = cls.newInstance();
/**
* Field[] getFields():获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
*/
Field[] fields = cls.getFields();
System.out.println(Arrays.toString(fields));
/**
* 获得某个类的自己声明的字段,即包括public、private和protected,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。
*/
Field[] declaredFields = cls.getDeclaredFields();
/**
* Constructor<?>[] getConstructors(): 获取有参构造器,返回公开的所有构造器
*/
Constructor<?>[] constructors = cls.getConstructors();
/**
* 获取指定的构造器,可以通过数据类型
*/
Constructor<?> constructor = cls.getConstructor(String.class, int.class,char.class);
/**
* 获取所有的公开的方法,包括构造方法
*/
Method[] methods = cls.getMethods();
/**
* 获取指定的方法,可以获得有参数的方法
* 后面是演示了方法的执行
*/
Method method = cls.getMethod("say", String.class);
method.invoke(cls.newInstance(), "小五");
/**
* 获取class对象的私有方法,并且执行
*/
Method methodPrivate = cls.getDeclaredMethod("methodPrivate");
// 改为允许访问私有方法
methodPrivate.setAccessible(true);
methodPrivate.invoke(cls.newInstance());
// 养成好习惯,用了之后我们还要给他改回去
methodPrivate.setAccessible(false);
/**
* 扫描注解
*/
boolean res = cls.isAnnotationPresent(AutoRunClass.class);
System.out.println(res?"被标记了":"没有被标记");
下面给几个小Demo
Person
类的结构
private String name = "李四";
private int age = 18;
private char sex = '男';
public Person() {
}
public Person(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
private void methodPrivate(){
System.out.println("私有方法");
}
@AutoRunMethod()
public void sayHello() {
System.out.println("你好" + name);
}
public void sayHi() {
System.out.println(name + "HI!");
}
public void say(String info) {
System.out.println(name + ":" + info);
}
public void say(String info, int count) {
for (int i = 0; i < count; i++) {
System.out.println(name + ":" + info);
}
}
public int sum(int a, int b){
return a+b;
}
关于方法method对象的常用
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请输入类名:");
String className = sc.nextLine();
Class<?> cls = Class.forName(className);
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法名:" + method.getName());
System.out.println("参数个数:" + method.getParameterCount());
System.out.println("参数类型:" + Arrays.toString(method.getParameterTypes()));
System.out.println(Arrays.toString(method.getTypeParameters()));
int result = method.getModifiers();// 获取方法的访问修饰符
switch (result){
case Modifier.PUBLIC:
System.out.println("public");
break;
case Modifier.PRIVATE:
System.out.println("private");
break;
case Modifier.PROTECTED:
System.out.println("protected");
break;
}
System.out.println("-------------------------");
}
}
反射关于注解常用
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请输入类名:");
String className = sc.nextLine();
Class<?> cls = Class.forName(className);
boolean clsRes = cls.isAnnotationPresent(AutoRunClass.class);
if (clsRes) {
Object obj = cls.newInstance();
// 获取符合条件类中的方法
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
// 判断方法是否符合条件
boolean methodRes = method.isAnnotationPresent(AutoRunMethod.class);
if (methodRes) {
method.invoke(obj);
} else {
System.out.println("该方法没有被注解标记:" + method.getName());
}
}
} else {
System.out.println("该类没有被标记");
}
}