【java】反射
反射
反射机制可以读取注解。
反射的概念
在 Java 中,通常情况下,我们在编译时就知道要使用的类和方法。但反射机制打破了这种常规,它允许程序在运行时动态地分析类、调用方法、操作字段等。也就是说,在运行时,程序可以根据需要来决定要使用哪个类、调用哪个方法、访问哪个字段,而不是在编译时就确定下来。
反射的核心类
Java 反射机制主要涉及以下几个核心类:
1、Class
类
Class类
是反射机制的基础,它代表一个类或接口。在 Java 中,每个类都有一个对应的Class对象,通过Class对象可以获取类的各种信息,如类名、父类、接口、方法、字段等**(可以理解为Class对象拥有对应类的所有信息)**。获取Class对象的常见方式有以下几种:
Class.forName("全限定类名")
:通过类的全限定名获取Class
对象。类名.class
:直接通过类名获取Class
对象。对象.getClass()
:通过对象的getClass()
方法获取Class
对象。
// 示例代码
try {
// 方式一:使用 Class.forName
Class<?> clazz1 = Class.forName("java.util.ArrayList");
// 方式二:使用 类名.class
Class<?> clazz2 = java.util.ArrayList.class;
// 方式三:使用 对象.getClass()
java.util.ArrayList list = new java.util.ArrayList();
Class<?> clazz3 = list.getClass();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2、Constructor
类:代表类的构造函数,通过 Class
对象的 getConstructor()
或 getDeclaredConstructor()
方法可以获取构造函数对象,然后使用 newInstance()
方法创建类的实例。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ConstructorExample {
public static void main(String[] args) {
try {
Class<?> clazz = String.class;
// 获取带有一个 String 参数的构造函数
Constructor<?> constructor = clazz.getConstructor(String.class);
// 创建对象
Object obj = constructor.newInstance("Hello");
System.out.println(obj);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
3、Method
类:代表类的方法,通过 Class
对象的 getMethod()
或 getDeclaredMethod()
方法可以获取方法对象,然后使用 invoke()
方法调用该方法。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodExample {
public static void main(String[] args) {
try {
Class<?> clazz = String.class;
// 获取 substring 方法
Method method = clazz.getMethod("substring", int.class, int.class);
String str = "HelloWorld";
// 调用方法
Object result = method.invoke(str, 0, 5);
System.out.println(result);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
4、Field
类:代表类的字段,通过 Class
对象的 getField()
或 getDeclaredField()
方法可以获取字段对象,然后使用 get()
和 set()
方法访问和修改字段的值。
import java.lang.reflect.Field;
class MyClass {
public int myField = 10;
}
public class FieldExample {
public static void main(String[] args) {
try {
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();
// 获取字段
Field field = clazz.getField("myField");
// 获取字段的值
int value = (int) field.get(obj);
System.out.println("Original value: " + value);
// 修改字段的值
field.set(obj, 20);
int newValue = (int) field.get(obj);
System.out.println("New value: " + newValue);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
反射的使用场景
- 框架开发:许多 Java 框架(如 Spring、Hibernate 等)都广泛使用反射机制。例如,Spring 框架通过反射实现依赖注入,在运行时动态地创建对象并注入依赖;Hibernate 框架通过反射来映射数据库表和 Java 类。
- 插件化开发:在插件化开发中,可以使用反射机制动态加载和使用插件类,实现程序的扩展性。
- 单元测试:在单元测试中,可以使用反射机制访问和调用类的私有方法和字段,方便进行测试。
反射的优缺点
优点
- 灵活性高:反射机制允许程序在运行时动态地处理类和对象,提高了程序的灵活性和可扩展性。
- 可扩展性强:可以在不修改现有代码的情况下,通过反射机制动态地加载和使用新的类和方法。
缺点
- 性能开销大:反射涉及到动态解析类和方法,相比直接调用,会带来一定的性能开销。
- 安全性问题:反射机制可以访问和修改类的私有成员,可能会破坏类的封装性,带来安全隐患。
- 代码可读性和维护性差:反射代码通常比较复杂,可读性和维护性较差。
得到Class类的几种方式
1. 使用 Class.forName()
方法
- 原理:
Class.forName()
是一个静态方法,它接收一个字符串参数,该参数为类的全限定名(包含包名)。此方法会尝试加载指定名称的类,并返回对应的Class
对象。如果指定的类不存在或者在加载过程中出现问题,会抛出ClassNotFoundException
异常。 - 适用场景:常用于动态加载类,例如在配置文件中指定类名,程序在运行时根据配置文件中的类名动态加载相应的类。
- 示例代码
public class ForNameExample {
public static void main(String[] args) {
try {
// 通过全限定名获取 Class 对象
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println(clazz.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2. 使用 类名.class
语法
- 原理:在 Java 中,每个类都有一个隐含的静态成员变量
class
,通过类名.class
可以直接获取该类对应的Class
对象。这种方式是在编译时就确定了要获取的Class
对象,不需要进行类的加载操作。 - 适用场景:当在代码中明确知道要操作的类时,使用这种方式非常简洁和方便。
- 示例代码
public class ClassLiteralExample {
public static void main(String[] args) {
// 直接通过类名获取 Class 对象
Class<?> clazz = java.util.ArrayList.class;
System.out.println(clazz.getName());
}
}
3. 使用对象的 getClass()
方法
- 原理:
Object
类是所有类的父类,Object
类中定义了getClass()
方法,该方法返回调用该方法的对象所属类的Class
对象。因此,任何 Java 对象都可以调用getClass()
方法来获取其所属类的Class
对象。 - 适用场景:当已经有一个对象实例,需要获取该对象所属类的信息时,使用这种方式很合适。
- 示例代码
import java.util.ArrayList;
public class GetClassExample {
public static void main(String[] args) {
// 创建对象
ArrayList<String> list = new ArrayList<>();
// 通过对象的 getClass() 方法获取 Class 对象
Class<?> clazz = list.getClass();
System.out.println(clazz.getName());
}
}
4. 使用基本数据类型的 TYPE
字段
- 原理:对于基本数据类型(如
int
、char
等),Java 为它们提供了对应的包装类,并且每个包装类都有一个静态常量TYPE
,该常量是一个Class
对象,代表对应的基本数据类型。 - 适用场景:在处理基本数据类型和包装类的反射操作时会用到。
- 示例代码
public class PrimitiveTypeExample {
public static void main(String[] args) {
// 获取 int 基本数据类型的 Class 对象
Class<?> intClass = int.class;
System.out.println(intClass.getName());
// 通过包装类的 TYPE 字段获取 int 基本数据类型的 Class 对象
Class<?> intClass2 = Integer.TYPE;
System.out.println(intClass2.getName());
}
}
哪些类型可以有Class对象
1. 类(class
)
普通的 Java 类,无论是自定义类还是 Java 标准库中的类,都有对应的 Class
对象。
// 自定义类
class MyClass {}
public class ClassForClassExample {
public static void main(String[] args) {
// 获取自定义类的 Class 对象
Class<?> myClassClass = MyClass.class;
System.out.println(myClassClass.getName());
// 获取 Java 标准库中类的 Class 对象
Class<?> stringClass = String.class;
System.out.println(stringClass.getName());
}
}
2. 接口(interface
)
Java 中的接口同样拥有对应的 Class
对象。
// 定义一个接口
interface MyInterface {}
public class ClassForInterfaceExample {
public static void main(String[] args) {
// 获取接口的 Class 对象
Class<?> myInterfaceClass = MyInterface.class;
System.out.println(myInterfaceClass.getName());
// 获取 Java 标准库中接口的 Class 对象
Class<?> listInterfaceClass = java.util.List.class;
System.out.println(listInterfaceClass.getName());
}
}
3. 数组(array
)
无论数组的元素类型是基本数据类型还是引用类型,数组都有对应的 Class
对象。数组的 Class
对象的名称包含元素类型和维度信息。
public class ClassForArrayExample {
public static void main(String[] args) {
// 基本数据类型数组
int[] intArray = new int[5];
Class<?> intArrayClass = intArray.getClass();
System.out.println(intArrayClass.getName());
// 引用类型数组
String[] stringArray = new String[5];
Class<?> stringArrayClass = stringArray.getClass();
System.out.println(stringArrayClass.getName());
}
}
4. 基本数据类型(primitive type
)
Java 的 8 种基本数据类型(byte
、short
、int
、long
、float
、double
、char
、boolean
)都有对应的 Class
对象。可以通过 .class
语法或者包装类的 TYPE
字段来获取。
public class ClassForPrimitiveExample {
public static void main(String[] args) {
// 通过 .class 语法获取基本数据类型的 Class 对象
Class<?> intClass = int.class;
System.out.println(intClass.getName());
// 通过包装类的 TYPE 字段获取基本数据类型的 Class 对象
Class<?> doubleClass = Double.TYPE;
System.out.println(doubleClass.getName());
}
}
5. 枚举(enum
)
枚举类型是一种特殊的类,也有对应的 Class
对象。
// 定义一个枚举
enum MyEnum {
VALUE1, VALUE2
}
public class ClassForEnumExample {
public static void main(String[] args) {
// 获取枚举的 Class 对象
Class<?> myEnumClass = MyEnum.class;
System.out.println(myEnumClass.getName());
}
}
6. 注解(annotation
)
注解是 Java 5 引入的一种元数据机制,注解类型也有对应的 Class
对象。
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)
@interface MyAnnotation {}
public class ClassForAnnotationExample {
public static void main(String[] args) {
// 获取注解的 Class 对象
Class<?> myAnnotationClass = MyAnnotation.class;
System.out.println(myAnnotationClass.getName());
}
}
7. void
类型
void
类型也有对应的 Class
对象,可通过 void.class
来获取。
public class ClassForVoidExample {
public static void main(String[] args) {
// 获取 void 类型的 Class 对象
Class<?> voidClass = void.class;
System.out.println(voidClass.getName());
}
}