目录
一、反射的应用场景
Java 反射机制在动态加载类、探索类的属性和方法、处理注解、序列化等方面具有广泛的应用。它为开发人员提供了更大的灵活性和可扩展性,使得在运行时动态地操作和扩展类成为可能。
下面是反射的适用场景:
1、需要动态加载类
使用反射可以在运行时动态加载类,而不需要在编译时确定类的名称。这在某些情况下非常有用,例如在插件化系统中,可以根据配置或用户需求动态加载并实例化不同的类。
2、探索和操作类的属性
反射提供了一种检查和操作类的属性(字段)的能力。通过反射,可以获取类的字段名称、类型、修饰符等信息,并且可以读取和修改类的字段值。
3、调用类的方法
反射可以用于调用类的方法,包括公共方法、私有方法和静态方法。这使得在运行时根据条件或配置动态地调用不同的方法成为可能。
3、注解处理
反射可以用于处理注解。可以检查类、方法、字段上的注解,并根据注解的信息执行相应的逻辑。这在编写通用框架或编写基于注解的扩展机制时非常有用。
5、序列化和反序列化:
Java 的序列化和反序列化机制依赖于反射。通过反射,可以动态地读取和写入对象的字段值,实现对象的序列化和反序列化。
6、单元测试:
在编写单元测试时,反射可以用于访问和修改私有字段和方法,以便进行测试。这样可以绕过访问限制,使得测试更加灵活和全面。
二、反射不适用场景
尽管反射提供了强大的功能,但它也带来了一些性能和安全方面的考虑。使用反射时,需要小心处理访问权限,避免滥用反射导致性能下降或不安全的操作。在性能要求高、安全敏感、编译时确定类和方法、对象创建和代码混淆等场景下,可能不适合使用反射。
下面是反射的不适用场景:
1、性能要求高的场景
相对于直接调用方法或访问字段,使用反射会导致性能上的损失。反射需要进行额外的查找和验证操作,因此在对性能要求非常高的场景下,应该避免过度使用反射。
2、安全敏感的场景
反射可以绕过访问修饰符的限制,访问和修改私有成员。在安全敏感的场景中,可能不希望代码有这种能力,因为它可以破坏封装性和引入潜在的安全风险。
3、编译时确定类和方法的场景
如果在编译时已经知道要使用的类和方法,并且它们的结构是固定的,那么使用反射可能是不必要的。反射更适合于在运行时根据动态条件或配置来处理不同的类和方法。
4、对象创建的场景
在大多数情况下,可以使用普通的类实例化方式(通过构造函数或静态工厂方法)来创建对象。反射的对象实例化方法(newInstance())相对较慢且不够直观,因此在对象创建的场景中可能不需要使用反射。
5、混淆代码的场景
如果计划对代码进行混淆,以增加代码的安全性和减小代码体积,那么使用反射将会遇到困难。因为反射依赖于类和方法的名称,混淆会改变这些名称,导致反射无法正常工作。
三、动态加载类创建类的实例(对象)
反射机制允许在运行时动态地获取类的信息、调用类的方法和访问类的字段,而不需要在编译时确定类的具体名称。
Class.forName() 方法接受一个字符串参数,该字符串表示要加载的类的完全限定名(Fully Qualified Name)。
先提供一个测试类,后面所有代码均需此类不再赘述:
package com.test;
public class TestInfo{
public TestInfo(){
}
public TestInfo(int id, String name) {
this.id = id;
this.name = name;
}
private String Test1;
private String Test2;
public String Test3;
public String Test4;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private void Test1() {
}
public void Test2() {
}
public String Test3(String value){
return value + "-成功执行";
}
}
测试代码:
try {
//1、通过 完全限定名 加载类的Class对象
Class c1 = Class.forName("com.test.TestInfo");
//2、通过类的 class 属性获取类的Class对象
Class c2 = TestInfo.class;
//3、通过对象获取类的Class对象
TestInfo testInfo = new TestInfo();
Class c3 = testInfo.getClass();
//4、通过Class对象创建类的实例
TestInfo testInfo1 = (TestInfo) c1.newInstance();
TestInfo testInfo2 = (TestInfo) c1.newInstance();
TestInfo testInfo3 = (TestInfo) c1.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
四、通过反射构造函数创建类的实例(对象)
首先获取TestInfo
的 Class
对象,然后使用 getConstructor()
方法获取默认无参构造函数的 Constructor
对象。接最后使用 newInstance()
方法创建实例,并强制类型转换为 TestInfo
类型。
测试代码:
Class c = TestInfo.class;
try {
Constructor constructor1 = c.getConstructor();
Constructor constructor2 = c.getConstructor(new Class[]{ int.class, String.class });
TestInfo testInfo1 = (TestInfo)constructor1.newInstance();
TestInfo testInfo2 = (TestInfo)constructor2.newInstance(new Object[]{ 1, "构造方法传入值"});
System.out.println("通过无参构造函数创建的类的值:"+testInfo1.getName());
System.out.println("通过有参构造函数创建的类的值:"+testInfo2.getName());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
五、反射操作成员变量
使用反射可以获取和操作类的字段(成员变量)。
以下是几种常用的反射字段操作方法:
1、getDeclaredField(String name): 获取指定名称的类的字段(包括私有字段)。
2、getField(String name): 获取指定名称的类的公共字段。
3、getDeclaredFields(): 获取类中声明的所有字段(包括私有字段)。
4、getFields(): 获取类中的所有公共字段。
5、set(Object obj, Object value): 设置指定对象的字段值。
6、get(Object obj): 获取指定对象的字段值。
测试代码:
TestInfo testInfo = new TestInfo();
testInfo.Test3 = "初始值";
Class c = testInfo.getClass();
//获取类的公共(Public)字段(Field)
Field[] fields1 = c.getFields();
for(Field field : fields1){
System.out.println("通过反射获到的公共字段名称:"+field.getName());
}
//获取类的所有字段(Field)
Field[] fields2 = c.getDeclaredFields();
for(Field field : fields1){
System.out.println("通过反射获到的所有字段名称:"+field.getName());
}
//下面的示例是通过反射来获取和设置字段的值。当然私有属性只需将 getField 换成 getDeclaredField
Field f = null;
try {
f = c.getField("Test3");
System.out.println("通过反射获到的单个字段名称:"+f.getName());
Object value = f.get(testInfo);
System.out.println("通过反射获到的单个字段的值:"+value);
f.set(testInfo, "通过反射设置的名称");
System.out.println("查看对象实际值:"+testInfo.Test3);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
六、反射操作方法
使用反射可以获取和调用类的方法。
以下是几种常用的反射方法操作方法:
1、getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定名称和参数类型的类的方法(包括私有方法)。
2、getMethod(String name, Class<?>... parameterTypes): 获取指定名称和参数类型的类的公共方法。
3、getDeclaredMethods(): 获取类中声明的所有方法(包括私有方法)。
4、invoke(Object obj, Object... args): 调用指定对象的方法。
测试代码:
TestInfo testInfo = new TestInfo();
testInfo.Test3 = "初始值";
Class c = testInfo.getClass();
//获取类的公共(Public)方法(Method)
Method[] methods1 = c.getMethods();
for(Method method : methods1){
System.out.println("通过反射获到的公共方法名称:"+method.getName());
}
//获取类的所有方法(Method)
Method[] methods2 = c.getDeclaredMethods();
for(Method method : methods2){
System.out.println("通过反射获到的所有方法名称:"+method.getName());
}
//下面的示例是通过反射来获取和调用公共方法。当然私有方法只需将 getMethod 换成 getDeclaredMethod
try {
//1、获取无参方法(Method)
Method m1 = c.getMethod("Test2");
//2、获取有参方法(Method)
Method m2 = c.getMethod("Test3", new Class[]{ String.class });
System.out.println("通过反射获到的单个方法名称:"+m2.getName());
//调用无参方法
m1.invoke(testInfo);
//调用有参方法
Object value = m2.invoke(testInfo, "通过反射调用方法");
System.out.println("通过反射调用方法获取返回:"+value);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}