引言: 在Java编程中,反射机制是一种强大的工具,它允许程序在运行时动态地获取和操作类的信息。通过反射,我们可以在运行时检查类、调用方法、访问属性,甚至可以在没有源代码的情况下创建并操作对象。本文将详细解释Java中的反射的用途以及使用步骤。
1.反射的用途:
-
动态加载类:反射允许程序在运行时动态加载需要使用的类,而不需要在编译时就确定类的名称。这对于开发框架和插件系统非常有用,可以根据配置文件或用户输入加载相应的类。
-
运行时检查类的结构:通过反射,我们可以在运行时检查类的成员变量、方法和构造函数等信息。这样可以避免硬编码类的结构,使代码更加灵活和可扩展。
-
调用方法和访问属性:反射机制可以使我们在运行时调用方法和访问属性,即使它们是私有的。这对于某些特定场景,如单元测试、动态代理等非常有用。
-
创建对象:通过反射,我们可以在运行时创建对象,即使我们没有类的源代码。这在某些情况下很有用,例如根据配置文件创建不同的对象实例。
2. 反射相关的类以及方法
类名 | 用途 |
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量/类的属性 |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
方法 | 用途 |
getClassLoader() | 获得类的加载器 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的) |
forName(String className) | 根据类名返回类的对象 |
newInstance() | 创建类的实例 |
getName() | 获得类的完整路径名字 |
方法 | 用途 |
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
方法 | 用途 |
getAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
方法 | 用途 |
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
方法 | 用途 |
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
3.使用步骤:
-
获取Class对象:要使用反射,首先需要获取对应类的Class对象。可以通过以下方式获取Class对象:
- 对象.getClass()方法:通过已有对象获取Class对象。
- 类名.class属性:直接使用类的class属性获取Class对象。
- Class.forName()方法:通过类的全限定名获取Class对象。
public class Test { public static void main(String[] args) { Class<?> c1; try { // 使用Class.forName方法获取类的Class对象 //该方法可能会抛出ClassNotFoundException异常。 c1 = Class.forName("demo1.Student"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } // 使用类名.class获取类的Class对象 Class<?> c2 = Student.class; // 使用实例对象的getClass方法获取类的Class对象 Student s = new Student(); Class<?> c3 = s.getClass(); //class对象只能存在一个!!! // 判断c1和c2是否是同一个对象 System.out.println(c1 == c2); // 输出:true // 判断c1和c3是否是同一个对象 System.out.println(c1 == c3); // 输出:true } } class Student { // 私有属性name private String name = "bit"; // 公共属性age public int age = 18; // 不带参数的公共构造方法 public Student() { System.out.println("这是一个构造方法"); } // 私有构造方法 private Student(String name, int age) { this.name = name; this.age = age; System.out.println("Student(String,name)"); } // 私有方法 private void eat() { System.out.println("i am eating"); } // 公共方法 public void sleep() { System.out.println("i am pig"); } // 私有方法 private void function(String str) { System.out.println(str); } // 重写toString方法 @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
-
获取类的成员:通过Class对象,可以获取类的构造函数、方法和字段等成员信息。可以使用以下方法获取类的成员:
- getConstructors()、getDeclaredConstructors():获取类的构造函数。
- getMethods()、getDeclaredMethods():获取类的方法。
- getFields()、getDeclaredFields():获取类的字段。
-
调用方法和访问属性:通过成员对象,可以调用方法和访问属性。如果成员是私有的,需要设置setAccessible(true)来取消访问限制。
-
创建对象:通过Class对象,可以使用newInstance()方法创建对象。如果类有参数化的构造函数,可以使用getConstructor()获取特定的构造函数,然后使用newInstance()方法创建对象。
-
性能考虑:虽然反射机制很灵活,但它相对于直接调用方法或访问属性来说效率较低,因为它需要在运行时进行动态解析。因此,在性能要求较高的场景中,应尽量避免过多地使用反射。
3.1 反射示例
1.使用反射创建对象
public static void main(String[] args) {
Class<?> c1;
try {
// 使用Class.forName方法获取类的Class对象
c1 = Class.forName("demo1.Student");
// 使用Class对象的newInstance方法创建类的实例
Student s = (Student) c1.newInstance();
// 打印实例的字符串表示形式
System.out.println(s);
} catch (ClassNotFoundException e) {
// 如果指定的类名不存在或无法加载,抛出ClassNotFoundException异常
throw new RuntimeException(e);
} catch (InstantiationException e) {
// 如果无法实例化该类,抛出InstantiationException异常
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// 如果无法访问该类的构造方法,抛出IllegalAccessException异常
throw new RuntimeException(e);
}
}
2. 获得带参数的构造方法
如果类没有不带参数的构造方法,就无法使用Class
对象的newInstance
方法来创建该类的实例对象。因为在调用newInstance
方法时,会自动调用类的不带参数的构造方法来创建对象。如果该类没有不带参数的构造方法,则会抛出InstantiationException
异常
public static void main(String[] args) {
Class<?> c1;
try {
// 使用Class.forName方法获取类的Class对象
c1 = Class.forName("demo1.Student");
// 获取声明的带参数的构造方法
Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class);
// 设置构造方法可访问(即使是私有构造方法)
constructor.setAccessible(true);
// 使用构造方法创建类的实例
Student s = (Student) constructor.newInstance("zhang", 19);
// 打印实例的字符串表示形式
System.out.println(s);
} catch (ClassNotFoundException e) {
// 如果指定的类名不存在或无法加载,抛出ClassNotFoundException异常
throw new RuntimeException(e);
} catch (InstantiationException e) {
// 如果无法实例化该类,抛出InstantiationException异常
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// 如果无法访问该类的构造方法,抛出IllegalAccessException异常
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
// 如果指定的构造方法不存在,抛出NoSuchMethodException异常
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
// 如果调用构造方法时发生错误,抛出InvocationTargetException异常
throw new RuntimeException(e);
}
}
3. 获取属性
public static void main(String[] args) {
Class<?> c1;
try {
// 使用Class.forName方法获取类的Class对象
c1 = Class.forName("demo1.Student");
// 获取声明的私有属性
Field field = c1.getDeclaredField("name");
// 使用默认构造函数创建类的实例
Student s = (Student) c1.newInstance();
// 设置属性可访问(即使是私有属性)
field.setAccessible(true);
// 设置属性值
field.set(s, "Ting");
// 打印实例的字符串表示形式
System.out.println(s);
} catch (ClassNotFoundException e) {
// 如果指定的类名不存在或无法加载,抛出ClassNotFoundException异常
throw new RuntimeException(e);
} catch (InstantiationException e) {
// 如果无法实例化该类,抛出InstantiationException异常
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// 如果无法访问该属性,抛出IllegalAccessException异常
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
// 如果指定的属性不存在,抛出NoSuchFieldException异常
throw new RuntimeException(e);
}
}
4. 获得方法
public static void main4(String[] args) {
Class<?> c1;
try {
// 使用Class.forName方法获取类的Class对象
c1 = Class.forName("demo1.Student");
// 获取声明的私有方法
Method m = c1.getDeclaredMethod("function", String.class);
// 使用默认构造函数创建类的实例
Student s = (Student) c1.newInstance();
// 设置方法可访问(即使是私有方法)
m.setAccessible(true);
// 调用方法,并传入参数
m.invoke(s, "这是一个反射的方法");
// 打印实例的字符串表示形式
System.out.println(s);
} catch (ClassNotFoundException e) {
// 如果指定的类名不存在或无法加载,抛出ClassNotFoundException异常
throw new RuntimeException(e);
} catch (InstantiationException e) {
// 如果无法实例化该类,抛出InstantiationException异常
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// 如果无法访问该方法,抛出IllegalAccessException异常
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
// 如果指定的方法不存在,抛出NoSuchMethodException异常
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
// 如果调用方法时发生异常,抛出InvocationTargetException异常
throw new RuntimeException(e);
}
}
结论: 反射机制是Java编程中非常有用的工具,它允许程序在运行时动态地获取和操作类的信息。通过反射,我们可以实现动态加载类、运行时检查类的结构、调用方法和访问属性,甚至可以在没有源代码的情况下创建并操作对象。但是在使用反射时,需要注意性能问题,并谨慎处理访问权限。熟练掌握反射机制,将有助于提升代码的灵活性和可扩展性