Java 反射详解

反射简介

反射(Reflection) 是 Java 被视为动态语言的关键。反射机制允许程序在执行期借助 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆内存的方法区中就产生了一个 Class 类型对象(一个类只有一个 Class 对象)。这个对象包含了完整的类的结构信息,可以通过这个对象看到类的结构。这个对象像一面镜子,透过镜子看到类的结构,所以形象地称为:反射

img

Class 类

Class 对象概述

  • Class 本身也是一个类。
  • Class 对象只能由系统建立。
  • 一个加载的类在 JVM 中只会有一个 Class 实例。
  • 一个 Class 对象对应的是一个加载到 JVM 的 .class 文件。
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成。
  • 通过 Class 可以完整地得到一个类中的所有被加载的结构。
  • Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象。

Class 类的常用方法

方法 说明
static Class<?> forName(String className) 返回指定类名的 Class 对象
Object newInstance() 调用缺省构造函数,返回该 Class 对象的一个实例
String getName() 返回此 Class 对象所表示的实体名称(含包名)
Class<?> getSuperclass() 返回当前 Class 对象的父类的 Class 对象
Class<?>[] getInterfaces() 获取当前 Class 对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Method[] getMethods() 返回包含某些 Method 对象的数组,反映此 Class 对象所表示的类或接口的公共方法
Field[] getFields() 返回包含某些 Field 对象的数组,反映此 Class 对象所表示的类或接口的公共字段
Constructor<?>[] getConstructors() 返回包含某些 Constructor 对象的数组,反映此 Class 对象所表示的类的所有公共构造器

更多方法可查阅 API 文档。


反射相关的主要 API

java.lang.Class 类与 java.lang.reflect 类库一起对反射的概念提供了支持。该类库包含了以下核心类(均实现了 Member 接口):

  • Field:代表类的成员变量
  • Method:代表类的方法
  • Constructor:代表类的构造器

这些类型的对象由 JVM 在运行时自动创建。


获取运行时类的完整结构

获取 Class 对象的四种方式

方式一:通过对象的 getClass() 方法
Student student = new Student();
Class c1 = student.getClass();
方式二:通过 Class.forName()(最常用)
Class c2 = null;
try {
    c2 = Class.forName("text.Student"); // 必须使用全限定名(包名+类名)
} catch (ClassNotFoundException e) {
    System.out.println("类未找到");
}
方式三:通过类名 .class
Class c3 = Student.class;

这种方式不会自动初始化该 Class 对象,因此更安全、性能更高。

方式四:基本内置类型的包装类有一个 TYPE 字段
Class<Integer> c4 = Integer.TYPE; // 表示 int 类型的 Class 对象
获得父类类型
Class c5 = c1.getSuperclass();

获取类的所有信息

// 获得 Class 对象
Class c1 = User.class;

// 获得类的名称
System.out.println(c1.getName());          // 包名 + 类名
System.out.println(c1.getSimpleName());    // 仅类名

// 获得类的属性
Field[] fs = c1.getFields();               // 只能获取 public 属性
fs = c1.getDeclaredFields();               // 获取所有属性(包括私有)
Field name = c1.getDeclaredField("name");  // 获取指定属性

// 获得类的方法
Method[] methods = c1.getMethods();        // 获取本类及其父类的所有 public 方法
methods = c1.getDeclaredMethods();         // 获取本类的所有方法(包括私有)
// 获取指定方法(需传入方法名及参数类型)
Method show = c1.getMethod("show", String.class);

// 获得构造器
Constructor[] constructors = c1.getConstructors();          // 所有 public 构造器
Constructor[] declaredConstructors = c1.getDeclaredConstructors(); // 所有构造器(包括私有)
// 获取指定构造器
Constructor constructor = c1.getDeclaredConstructor(String.class);

递归获取父类属性

Class tempClass = c1.getClass();
List<Field> fieldList = new ArrayList<>();
while (tempClass != null && !tempClass.getName().equals("java.lang.Object")) {
    fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
    tempClass = tempClass.getSuperclass(); // 递归获取父类
}

动态创建对象—通过反射

创建对象

Class c1 = User.class;

// 方式一:调用无参构造器
User user1 = (User) c1.newInstance();

// 方式二:通过指定构造器创建
Constructor constructor = c1.getDeclaredConstructor(String.class);
User user2 = (User) constructor.newInstance("wkf");

注意c1.newInstance() 内部实际上调用了类的无参构造器,因此类必须拥有无参构造器且访问权限足够。

调用方法

User user3 = (User) c1.newInstance();
Method show = c1.getDeclaredMethod("show", String.class);
show.invoke(user3, "锵锵锵"); // 激活方法

操作属性

User user4 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true); // 关闭权限检测,允许访问私有属性
name.set(user4, "wkf");

setAccessible 与性能

  • MethodFieldConstructor 对象都有 setAccessible(boolean flag) 方法。
  • 作用:启动或禁用访问安全检查的开关。
    • true:取消 Java 语言访问检查,可访问私有成员,同时提高反射效率。
    • false:实施访问检查。

性能对比:关闭访问检查后,反射调用的性能可大幅提升。

img

反射操作泛型

Java 采用泛型擦除机制,泛型信息仅在编译期有效,运行时会被擦除。为了通过反射操作泛型,Java 提供了几个新类型来表示泛型信息:

  • ParameterizedType:参数化类型,如 List<String>
  • GenericArrayType:元素类型是参数化类型或类型变量的数组,如 List<String>[]
  • TypeVariable:类型变量,如 T
  • WildcardType:通配符类型,如 ? extends Number

示例:获取方法参数的泛型信息

Method method = User.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type t : genericParameterTypes) {
    System.out.println(t);
    if (t instanceof ParameterizedType) {
        Type[] actualTypeArguments = ((ParameterizedType) t).getActualTypeArguments();
        for (Type tt : actualTypeArguments) {
            System.out.println("  泛型类型: " + tt);
        }
    }
}

反射操作注解

通过反射可以在运行时获取类、方法、字段上的注解信息,常用于框架开发(如 ORM)。

示例:自定义注解与反射读取

// 定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tablex {
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldx {
    String col();
    String type();
    int length();
}

// 使用注解
@Tablex("db_people")
class People {
    @Fieldx(col = "id", type = "int", length = 10)
    private int id;
    @Fieldx(col = "name", type = "varchar", length = 3)
    private String name;
    @Fieldx(col = "age", type = "int", length = 10)
    private int age;

    // 构造器、getter/setter、toString 省略
}

// 通过反射获取注解信息
public class TestAnnotation {
    public static void main(String[] args) throws Exception {
        Class<People> clazz = People.class;

        // 获取类上的注解
        Tablex table = clazz.getAnnotation(Tablex.class);
        System.out.println("表名: " + table.value());

        // 获取字段上的注解
        Field field = clazz.getDeclaredField("id");
        Fieldx fieldx = field.getAnnotation(Fieldx.class);
        System.out.println("字段列: " + fieldx.col() + ", 类型: " + fieldx.type() + ", 长度: " + fieldx.length());
    }
}

总结

  • 反射提供了在运行时动态获取类信息、创建对象、调用方法、访问字段的能力。
  • 核心是 Class 类及 java.lang.reflect 包下的 FieldMethodConstructor
  • 反射会带来一定的性能开销,但通过 setAccessible(true) 可以提升效率。
  • 反射广泛用于框架(如 Spring、MyBatis)、注解解析、动态代理等场景。

注意:反射虽然强大,但过度使用会破坏封装性,且存在安全风险,应合理使用。

posted @ 2022-10-19 09:07  克峰同学  阅读(63)  评论(0)    收藏  举报