- 作者:老汪软件技巧
- 发表时间:2024-11-07 07:01
- 浏览量:
什么是反射
反射是 Java 中的一个特性,它允许程序在运行时获取自身的信息,并动态地操作类或对象的属性、方法和构造函数。通过反射,我们可以在事先不知道确切类名的情况下实例化对象、调用方法和设置属性。
反射机制的核心是Class对象,它代表一个类。Java 虚拟机(JVM)在加载类时会自动创建这个Class对象。
JVM 如何创建一个类
当我们编写一个类并进行编译时,编译器会将其转换为存储在.class文件中的字节码。在类加载过程中,JVM 使用ClassLoader读取.class文件,将字节码加载到内存中,并根据这些信息创建相应的Class对象。由于每个类在 JVM 中只加载一次,所以每个类都对应一个唯一的Class对象。
示例
public class User extends People {
public String name;
private int age;
private static int staticFiled = 10;
private final String sex;
protected String protectedFiled;
static {
System.out.println("静态方法执行");
}
public User(String name, String sex) {
this.name = name;
this.sex = sex;
}
private void privateMethod() {
System.out.println("我是私有方法");
}
public void publicMethod() {
System.out.println("我是公共方法");
}
}
public class People {
public String publicFiled;
private String privateFiled;
}
获取Class对象的三种方式第一种方法通过类名使用.class获取类对象。这是在编译时完成的,所以明确指定了类型User,不会导致任何错误。使用这种方法获取对象不会触发类初始化;只有在访问类的静态成员或实例时才会进行初始化。
Class userClass = User.class;
实例化一个对象:
User userInstance = userClass.getDeclaredConstructor(String.class, String.class).newInstance("张三", "男");
第二种方法通过对象的getClass()方法获取类对象。这种方法适用于从已实例化的类对象中获取类对象。请注意,类型不是User,而是通配符?,因为Class对象是从User的实例中获取的,实例的具体类型只能在运行时确定,而不是在编译时。
User user = new User("张三", "男");
Class> userClass = user.getClass();
实例化一个对象:
Constructor> constructor = userClass.getConstructor(String.class, String.class);
User userInstance = (User) constructor.newInstance("张三", "男");
第三种方法使用静态方法Class.forName()通过全路径获取类对象。由于类型只能在运行时知道,所以类型是通配符?。通过这种方法获取类对象将立即触发类初始化。
Class> userClass = Class.forName("org.example.reflect.entity.User");
创建一个实例:
Constructor> constructor = userClass.getDeclaredConstructor(String.class, String.class);
User userInstance = (User) constructor.newInstance("张三", "男");
在 Java 中访问对象字段获取所有公共字段要获取所有公共字段,包括从父类继承的字段,使用getFields():
Field[] fields = user.getFields();
for (Field field : fields) {
System.out.println(field);
}
输出:
public java.lang.String org.example.reflect.entity.User.name
public java.lang.String org.example.reflect.entity.People.publicField
获取所有声明的字段要获取类中所有声明的字段,无论其访问级别如何,使用getDeclaredFields()。这不包括从超类继承的字段:
Field[] fields = user.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
输出:
public java.lang.String org.example.reflect.entity.User.name
private int org.example.reflect.entity.User.age
private final java.lang.String org.example.reflect.entity.User.sex
protected java.lang.String org.example.reflect.entity.User.protectedField
获取超类中的字段要获取超类中的字段,使用getSuperclass():
Field[] fields = user.getSuperclass().getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
输出:
public java.lang.String org.example.reflect.entity.People.publicField
private java.lang.String org.example.reflect.entity.People.privateField
获取特定字段要通过名称获取特定公共字段,使用getField(String name)。对于任何特定字段,无论其访问级别如何,使用getDeclaredField(String name)。
处理不存在的字段尝试访问不存在的字段不会产生编译时错误,但会在运行时抛出异常:
try {
Field nonExistentField = user.getDeclaredField("nonExistentField");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
输出:
java.lang.NoSuchFieldException: nonExistentField
设置字段值要设置私有静态字段的值,首先使其可访问:
Class> userClass = Class.forName("org.example.reflect.entity.User");
Field staticField = userClass.getDeclaredField("staticField");
staticField.setAccessible(true);
System.out.println(staticField.get(null));
如果字段是final的,仍然可以修改它:
Field field = userClass.getDeclaredField("sex");
field.setAccessible(true);
field.set(obj, "女生");
System.out.println(field.get(obj));
输出:
女生
访问方法
访问方法与访问字段类似:
总结
从上面的示例中可以看出,以Declared为前缀的方法(如getDeclaredField)用于检索所有字段或方法,无论其访问级别如何。相比之下,没有Declared的方法(如getField)仅检索公共字段或方法。
反射允许绕过访问控制检查。用private或final修饰的字段和方法可以被访问和修改,这破坏了封装性。因此,应该谨慎使用。