Skip to content

Java反射机制面试题

1. 反射的基本概念

问题:什么是反射?Java中的反射有什么作用?

答案

  • 反射:在运行时动态获取类的信息并操作类或对象的方法。
  • 作用
    • 在运行时获取类的信息,如方法、字段、构造方法等。
    • 在运行时创建对象、调用方法、访问字段。
    • 实现动态代理、注解处理、框架开发等。

2. 反射的API

问题:Java反射提供了哪些核心类?

答案

  • Class:表示类或接口的类。
  • Method:表示类的方法。
  • Field:表示类的字段。
  • Constructor:表示类的构造方法。
  • Modifier:表示修饰符。

3. 获取Class对象

问题:如何获取Class对象?

答案

java
// 方式1:通过类名
Class<?> clazz1 = String.class;

// 方式2:通过对象
String str = "Hello";
Class<?> clazz2 = str.getClass();

// 方式3:通过全限定名
Class<?> clazz3 = Class.forName("java.lang.String");

// 方式4:通过类加载器
Class<?> clazz4 = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");

4. 创建对象

问题:如何通过反射创建对象?

答案

java
// 方式1:通过Class对象的newInstance()方法(已过时)
Class<?> clazz = String.class;
String str = (String) clazz.newInstance();

// 方式2:通过Constructor对象
Constructor<?> constructor = clazz.getConstructor();
String str = (String) constructor.newInstance();

// 方式3:通过带参数的构造方法
Constructor<?> constructor = clazz.getConstructor(String.class);
String str = (String) constructor.newInstance("Hello");

5. 调用方法

问题:如何通过反射调用方法?

答案

java
// 获取方法
Class<?> clazz = String.class;
Method method = clazz.getMethod("substring", int.class, int.class);

// 调用方法
String str = "Hello, World!";
String result = (String) method.invoke(str, 0, 5);
System.out.println(result); // Hello

// 调用静态方法
Method staticMethod = clazz.getMethod("valueOf", int.class);
String result = (String) staticMethod.invoke(null, 123);
System.out.println(result); // 123

// 调用私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(obj);

6. 访问字段

问题:如何通过反射访问字段?

答案

java
// 获取字段
Class<?> clazz = Person.class;
Field field = clazz.getField("name");

// 获取字段值
Person person = new Person();
person.setName("John");
String name = (String) field.get(person);
System.out.println(name); // John

// 设置字段值
field.set(person, "Alice");
System.out.println(person.getName()); // Alice

// 访问私有字段
Field privateField = clazz.getDeclaredField("privateField");
privateField.setAccessible(true);
privateField.set(person, "value");

// 访问静态字段
Field staticField = clazz.getField("staticField");
staticField.set(null, "value");

7. 获取构造方法

问题:如何通过反射获取构造方法?

答案

java
// 获取所有公共构造方法
Class<?> clazz = Person.class;
Constructor<?>[] constructors = clazz.getConstructors();

// 获取所有构造方法(包括私有)
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();

// 获取指定参数类型的公共构造方法
Constructor<?> constructor = clazz.getConstructor(String.class);

// 获取指定参数类型的构造方法(包括私有)
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class);

// 创建对象
Person person = (Person) constructor.newInstance("John");

8. 获取方法

问题:如何通过反射获取方法?

答案

java
// 获取所有公共方法
Class<?> clazz = Person.class;
Method[] methods = clazz.getMethods();

// 获取所有方法(包括私有)
Method[] declaredMethods = clazz.getDeclaredMethods();

// 获取指定名称和参数类型的公共方法
Method method = clazz.getMethod("setName", String.class);

// 获取指定名称和参数类型的方法(包括私有)
Method declaredMethod = clazz.getDeclaredMethod("privateMethod");

// 获取父类方法
Method[] superMethods = clazz.getSuperclass().getMethods();

9. 获取字段

问题:如何通过反射获取字段?

答案

java
// 获取所有公共字段
Class<?> clazz = Person.class;
Field[] fields = clazz.getFields();

// 获取所有字段(包括私有)
Field[] declaredFields = clazz.getDeclaredFields();

// 获取指定名称的公共字段
Field field = clazz.getField("name");

// 获取指定名称的字段(包括私有)
Field declaredField = clazz.getDeclaredField("privateField");

// 获取父类字段
Field[] superFields = clazz.getSuperclass().getDeclaredFields();

10. 获取注解

问题:如何通过反射获取注解?

答案

java
// 获取类上的注解
Class<?> clazz = Person.class;
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);

// 获取方法上的注解
Method method = clazz.getMethod("method");
MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);

// 获取字段上的注解
Field field = clazz.getField("name");
MyAnnotation fieldAnnotation = field.getAnnotation(MyAnnotation.class);

// 获取所有注解
Annotation[] annotations = clazz.getAnnotations();

11. 获取父类和接口

问题:如何通过反射获取父类和接口?

答案

java
// 获取父类
Class<?> clazz = Person.class;
Class<?> superClass = clazz.getSuperclass();
System.out.println(superClass); // class Object

// 获取所有接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> interfaceClass : interfaces) {
    System.out.println(interfaceClass);
}

12. 获取泛型信息

问题:如何通过反射获取泛型信息?

答案

java
// 获取方法的返回类型
Method method = clazz.getMethod("method");
Type returnType = method.getGenericReturnType();

// 获取方法的参数类型
Type[] parameterTypes = method.getGenericParameterTypes();

// 获取字段的类型
Field field = clazz.getField("list");
Type fieldType = field.getGenericType();

// 判断是否是参数化类型
if (fieldType instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) fieldType;
    Type[] typeArguments = parameterizedType.getActualTypeArguments();
    for (Type typeArgument : typeArguments) {
        System.out.println(typeArgument);
    }
}

13. 动态代理

问题:什么是动态代理?如何实现?

答案

  • 动态代理:在运行时动态创建代理类。
  • 实现方式
java
// 创建InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}

// 创建代理对象
public interface UserService {
    void addUser(String name);
}

public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("Add user: " + name);
    }
}

public class Test {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(userService);
        UserService proxy = (UserService) Proxy.newProxyInstance(
            userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(),
            handler
        );
        proxy.addUser("John");
    }
}

14. 反射的性能

问题:反射对性能有什么影响?如何优化?

答案

  • 性能影响:反射比直接调用慢,因为需要进行安全检查、参数类型转换等。
  • 优化方法
    • 缓存Class对象、Method对象、Field对象。
    • 使用setAccessible(true)跳过安全检查。
    • 使用MethodHandle代替反射。

示例

java
// 缓存Method对象
private static final Method METHOD;
static {
    try {
        METHOD = String.class.getMethod("substring", int.class, int.class);
        METHOD.setAccessible(true);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}

// 使用缓存的Method对象
String result = (String) METHOD.invoke(str, 0, 5);

15. 反射的安全性

问题:反射有什么安全问题?如何防范?

答案

  • 安全问题
    • 可以访问私有成员,破坏封装性。
    • 可以绕过安全检查,造成安全隐患。
  • 防范方法
    • 使用SecurityManager限制反射权限。
    • 避免在生产环境中使用反射。
    • 对反射操作进行审计和监控。

16. 反射的应用场景

问题:反射有哪些应用场景?

答案

  • 框架开发:Spring、Hibernate等框架大量使用反射。
  • 动态代理:AOP、RPC等。
  • 注解处理:编译时注解处理、运行时注解处理。
  • 序列化和反序列化:JSON、XML等。
  • 依赖注入:Spring的IoC容器。
  • 单元测试:Mock框架。
  • 插件系统:动态加载和调用插件。

17. 反射的局限性

问题:反射有哪些局限性?

答案

  • 性能较差,比直接调用慢。
  • 破坏封装性,可以访问私有成员。
  • 代码可读性差,难以理解和维护。
  • 安全性差,可以绕过安全检查。
  • 编译时无法检查类型安全。

18. 反射的最佳实践

问题:使用反射的最佳实践有哪些?

答案

  • 尽量避免使用反射,只有在必要时使用。
  • 缓存反射对象,避免重复创建。
  • 使用setAccessible(true)提高性能。
  • 使用try-catch处理反射异常。
  • 对反射操作进行日志记录和监控。
  • 在生产环境中限制反射权限。

19. 反射和注解

问题:反射和注解有什么关系?

答案

  • 注解是元数据,可以通过反射在运行时获取。
  • 反射可以获取类、方法、字段上的注解信息。
  • 注解处理可以使用反射来处理注解。

示例

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

// 使用注解
@MyAnnotation("Hello")
public class MyClass {
}

// 通过反射获取注解
Class<?> clazz = MyClass.class;
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // Hello

20. 反射和泛型

问题:反射和泛型有什么关系?

答案

  • 泛型在编译时会被擦除,运行时无法获取泛型类型信息。
  • 反射可以通过Type接口获取泛型信息。
  • ParameterizedType表示参数化类型。

示例

java
public class MyClass<T> {
    private List<T> list;
    
    public void method(List<String> list) {
    }
}

// 通过反射获取泛型信息
Class<?> clazz = MyClass.class;
Field field = clazz.getDeclaredField("list");
Type fieldType = field.getGenericType();

if (fieldType instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) fieldType;
    Type[] typeArguments = parameterizedType.getActualTypeArguments();
    for (Type typeArgument : typeArguments) {
        System.out.println(typeArgument); // T
    }
}

Method method = clazz.getMethod("method", List.class);
Type parameterType = method.getGenericParameterTypes()[0];

if (parameterType instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) parameterType;
    Type[] typeArguments = parameterizedType.getActualTypeArguments();
    for (Type typeArgument : typeArguments) {
        System.out.println(typeArgument); // class java.lang.String
    }
}

21. 反射和数组

问题:如何通过反射操作数组?

答案

java
// 创建数组
Class<?> componentType = String.class;
int length = 10;
Object array = Array.newInstance(componentType, length);

// 设置数组元素
Array.set(array, 0, "Hello");
Array.set(array, 1, "World");

// 获取数组元素
String element = (String) Array.get(array, 0);
System.out.println(element); // Hello

// 获取数组长度
int arrayLength = Array.getLength(array);
System.out.println(arrayLength); // 10

22. 反射和枚举

问题:如何通过反射操作枚举?

答案

java
// 获取枚举类
Class<?> enumClass = MyEnum.class;

// 获取所有枚举值
Object[] enumConstants = enumClass.getEnumConstants();
for (Object enumConstant : enumConstants) {
    System.out.println(enumConstant);
}

// 获取指定枚举值
MyEnum value = MyEnum.valueOf("VALUE1");
System.out.println(value); // VALUE1

// 获取枚举的名称和序号
Field nameField = enumClass.getDeclaredField("name");
nameField.setAccessible(true);
String name = (String) nameField.get(value);
System.out.println(name); // VALUE1

Field ordinalField = enumClass.getDeclaredField("ordinal");
ordinalField.setAccessible(true);
int ordinal = (int) ordinalField.get(value);
System.out.println(ordinal); // 0

23. 反射和类加载器

问题:反射和类加载器有什么关系?

答案

  • 反射需要通过类加载器加载类。
  • 可以通过类加载器动态加载类。
  • 可以使用自定义类加载器加载类。

示例

java
// 通过类加载器加载类
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");

// 通过自定义类加载器加载类
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 自定义加载逻辑
        return defineClass(name, classData, 0, classData.length);
    }
}

MyClassLoader classLoader = new MyClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");

24. 反射和序列化

问题:反射和序列化有什么关系?

答案

  • 序列化和反序列化可以使用反射来获取和设置字段值。
  • JSON、XML等序列化库使用反射来转换对象。

示例

java
public class JsonSerializer {
    public static String toJson(Object obj) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);
            String name = field.getName();
            Object value = field.get(obj);
            
            sb.append("\"").append(name).append("\":");
            if (value instanceof String) {
                sb.append("\"").append(value).append("\"");
            } else {
                sb.append(value);
            }
            
            if (i < fields.length - 1) {
                sb.append(",");
            }
        }
        
        sb.append("}");
        return sb.toString();
    }
}

25. 反射的替代方案

问题:反射的替代方案有哪些?

答案

  • MethodHandle:Java 7引入的更高效的反射替代方案。
  • Lambda表达式:用于函数式编程,可以替代部分反射场景。
  • 代码生成:使用字节码生成库,如ASM、Byte Buddy等。
  • 编译时注解处理:在编译时生成代码,避免运行时反射。

示例

java
// 使用MethodHandle
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(String.class, int.class, int.class);
MethodHandle methodHandle = lookup.findVirtual(String.class, "substring", methodType);
String result = (String) methodHandle.invoke("Hello, World!", 0, 5);
System.out.println(result); // Hello