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