likes
comments
collection
share

掌握Java反射:打开面向对象编程的新大门

作者站长头像
站长
· 阅读数 24

一道面试题:请问 Java 中的反射是什么?有什么作用和优势?如何使用反射?

你们可以先尝试回答回答

如果不会我们就开始下面的文章

引言

什么是反射

Java反射(Reflection)是指在运行时获取对象信息(例如类名、方法、属性等),并且可以动态操作该对象,而无需事先知道该对象的静态类型。它允许程序在运行时检查对象和类,并且可以调用对象的方法、获取对象的属性和构造函数,甚至可以动态创建新的对象和数组。

反射的作用

Java反射的作用是使得程序能够在运行时动态地操作对象和类,而不需要事先知道这些信息,这种能力为程序设计带来了很大的灵活性。使用反射可以实现很多高级功能,比如动态代理注解处理、JavaBean属性的自动设置等等。但是,由于反射操作具有较高的开销,因此在需要性能的场合应该谨慎使用。

背景知识

Java反射的基本原理是通过使用Java的反射API来获取类的信息。Java的反射API提供了一个Class类,它可以用来描述类的信息,例如类名、构造函数、方法和字段等。通过使用Class类的方法,可以获取一个类的所有信息。

Java反射可以让程序在运行时动态地获取类的信息并进行操作,这使得程序的灵活性更高。但是,由于反射会牺牲一定的性能,所以在性能要求较高的情况下,应该尽量避免使用反射。

讲解Java反射的核心概念和API

Java反射的核心概念主要涉及到以下几个类:ClassConstructorFieldMethod。以下是这些类的主要API及其用法:

  1. Class类

    • 作用:表示正在运行的Java应用程序中的类和接口。

    • 获取Class对象的方法:

      • Class.forName(String className):根据类名加载类并获取Class对象。
      • object.getClass():根据已知对象获取Class对象。
      • ClassName.class:直接获取类的Class对象。
  2. Constructor类

    • 作用:描述类的构造方法。

    • 获取构造方法对象的方法:

      • Class.getConstructor(Class<?>... parameterTypes):获取类的public构造方法。
      • Class.getDeclaredConstructor(Class<?>... parameterTypes):获取类的所有构造方法,包括private的。
    • 创建对象:

      • Constructor.newInstance(Object... initargs):使用构造方法创建新的对象实例。
  3. Field类

    • 作用:描述类的字段(成员变量)。

    • 获取字段对象的方法:

      • Class.getField(String name):获取类的public字段。
      • Class.getDeclaredField(String name):获取类的所有字段,包括private的。
    • 获取和设置字段值:

      • Field.get(Object obj):获取对象的字段值。
      • Field.set(Object obj, Object value):设置对象的字段值。
  4. Method类

    • 作用:描述类的方法。

    • 获取方法对象的方法:

      • Class.getMethod(String name, Class<?>... parameterTypes):获取类的public方法。
      • Class.getDeclaredMethod(String name, Class<?>... parameterTypes):获取类的所有方法,包括private的。
    • 调用方法:

      • Method.invoke(Object obj, Object... args):调用方法并返回结果。

注意:在使用getDeclaredConstructorgetDeclaredFieldgetDeclaredMethod时,你可能需要调用setAccessible(true)方法来访问private成员。

以上是Java反射中的核心API。在实际使用过程中,你可能还需要了解其他API,例如Class.getModifiers()Class.getSuperclass()等。但这些API不属于反射的核心功能,所以在此不详细列举。

api详解

Class

Class 类是 Java 反射的核心类,它表示正在运行的 Java 应用程序中的类和接口。以下是 Class 类中的一些重要方法:

获得Class对象:

//        获得Class对象
//        1.通过类名.class
        Class<?> cls1 = Person.class;
        
//        2.通过Class.forName()
        Class<?> cls2 = Class.forName("Reflection.bean.Person");
       
//        3.通过对象.getClass()
        Person person = new Person();
        Class<?> cls3 = person.getClass();

//        4.通过类加载器
        Class<?> cls4 =Thread.currentThread().getContextClassLoader().loadClass("Reflection.bean.Person");
  1. 获取类的名称:

    • String getName():返回类的全限定名(包含包名)。
    • String getSimpleName():返回类的简单名(不包含包名)。
    • String getCanonicalName():返回类的规范名(类似于全限定名,但对于内部类和数组类有所不同)。
  2. 加载和获取类:

    • static Class<?> forName(String className):根据类名加载类并获取其 Class 对象。
    • static Class<?> forName(String name, boolean initialize, ClassLoader loader):根据类名、是否初始化以及类加载器加载类并获取其 Class 对象。
  3. 获取类的修饰符:

    • int getModifiers():返回类的修饰符,例如 public、private、abstract 等。

    Modifier这个类里有判断返回数字是什么类型的方法

  4. 获取类的父类和接口:

    • Class<?> getSuperclass():返回类的父类。
    • Class<?>[] getInterfaces():返回类实现的接口。
  5. 获取类的构造方法、字段和方法:

    • Constructor<?>[] getConstructors():返回类的 public 构造方法。
    • Constructor<?>[] getDeclaredConstructors():返回类的所有构造方法,包括 private 的。
    • Field[] getFields():返回类的 public 字段。
    • Field[] getDeclaredFields():返回类的所有字段,包括 private 的。
    • Method[] getMethods():返回类的 public 方法。
    • Method[] getDeclaredMethods():返回类的所有方法,包括 private 的。
  6. 获取类的注解:

    • Annotation[] getAnnotations():返回类的注解。
    • Annotation[] getDeclaredAnnotations():返回类的所有注解,包括继承自父类的。
    • T getAnnotation(Class<T> annotationClass):返回类的指定类型的注解。
  7. 创建类的实例:

    • T newInstance():使用默认构造方法创建类的实例。在 Java 9 之后被弃用,建议使用 Constructor.newInstance()
  8. 判断类的关系:

    • boolean isAssignableFrom(Class<?> cls):判断当前类是否为参数类的超类或接口。
    • boolean isInstance(Object obj):判断指定对象是否为当前类的实例。
    • boolean isInterface():判断当前类是否为接口。
    • boolean isPrimitive():判断当前类是否为基本类型。
    • boolean isArray():判断当前类是否为数组。

Constructor

Constructor 类是 Java 反射中的一个重要类,用于表示类的构造方法。以下是 Constructor 类中的一些重要方法:

  1. 获取所有的构造方法:使用 Class 类的 getConstructors() 方法获取类的所有公共构造方法,或使用 getDeclaredConstructors() 方法获取类的所有构造方法(包括私有、受保护和默认访问权限的构造方法)。 示例:
Class<?> clazz = SomeClass.class;
Constructor<?>[] constructors = clazz.getConstructors(); // 获取所有公共构造方法
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 获取所有构造方法(包括非公共构造方法)
  1. 获取特定的构造方法:使用 Class 类的 getConstructor() 方法获取类的特定公共构造方法,或使用 getDeclaredConstructor() 方法获取类的特定构造方法(包括私有、受保护和默认访问权限的构造方法)。这些方法需要传入一个表示构造方法参数类型的 Class 对象数组。
Class<?> clazz = SomeClass.class;

try {
    Constructor<?> constructorWithNoArgs = clazz.getConstructor(); // 获取无参数的公共构造方法
    Constructor<?> constructorWithArgs = clazz.getConstructor(String.class, int.class); // 获取带有 String 和 int 参数的公共构造方法
    
    Constructor<?> declaredConstructorWithNoArgs = clazz.getDeclaredConstructor(); // 获取无参数的构造方法(包括非公共构造方法)
    Constructor<?> declaredConstructorWithArgs = clazz.getDeclaredConstructor(String.class, int.class); // 获取带有 String 和 int 参数的构造方法(包括非公共构造方法)
} catch (NoSuchMethodException e) {
    System.err.println("构造方法未找到: " + e.getMessage());
}

  1. 获取构造方法的名称和修饰符:

    • String getName():返回构造方法的名称,即类名。
    • int getModifiers():返回构造方法的修饰符,例如 public、private、protected 等。
  2. 获取构造方法的参数类型和异常类型:

    • Class<?>[] getParameterTypes():返回构造方法的参数类型。
    • Type[] getGenericParameterTypes():返回构造方法的泛型参数类型。
    • Class<?>[] getExceptionTypes():返回构造方法抛出的异常类型。
    • Type[] getGenericExceptionTypes():返回构造方法抛出的泛型异常类型。
  3. 获取和设置构造方法的访问权限:

    • boolean isAccessible():返回构造方法的可访问状态。
    • void setAccessible(boolean flag):设置构造方法的可访问状态。如果要访问 private 构造方法,需要将 flag 设置为 true
  4. 创建类的实例:

    • T newInstance(Object... initargs):使用构造方法创建类的实例。initargs 参数表示构造方法的参数值。
  5. 获取构造方法的注解:

    • Annotation[] getAnnotations():返回构造方法的注解。
    • Annotation[] getDeclaredAnnotations():返回构造方法的所有注解。
    • T getAnnotation(Class<T> annotationClass):返回构造方法的指定类型的注解。
    • Annotation[][] getParameterAnnotations():返回构造方法参数的注解。

Field

Field 类是 Java 反射中的一个重要类,用于表示类的字段(成员变量)。以下是 Field 类中的一些重要方法:

  1. 获取字段的名称和修饰符:

    • String getName():返回字段的名称。
    • int getModifiers():返回字段的修饰符,例如 public、private、protected、static 等。
  2. 获取字段的类型:

    • Class<?> getType():返回字段的类型。
    • Type getGenericType():返回字段的泛型类型。
  3. 获取和设置字段的值:

    • Object get(Object obj):返回指定对象上该字段的值。对于静态字段,obj 参数可以为 null
    • void set(Object obj, Object value):为指定对象上的该字段设置值。对于静态字段,obj 参数可以为 null
    • boolean getBoolean(Object obj)byte getByte(Object obj)char getChar(Object obj)double getDouble(Object obj)float getFloat(Object obj)int getInt(Object obj)long getLong(Object obj)short getShort(Object obj):分别返回指定对象上该字段的布尔值、字节值、字符值、双精度浮点值、单精度浮点值、整数值、长整数值和短整数值。对于静态字段,obj 参数可以为 null
    • void setBoolean(Object obj, boolean z)void setByte(Object obj, byte b)void setChar(Object obj, char c)void setDouble(Object obj, double d)void setFloat(Object obj, float f)void setInt(Object obj, int i)void setLong(Object obj, long l)void setShort(Object obj, short s):分别为指定对象上的该字段设置布尔值、字节值、字符值、双精度浮点值、单精度浮点值、整数值、长整数值和短整数值。对于静态字段,obj 参数可以为 null
  4. 获取和设置字段的访问权限:

    • boolean isAccessible():返回字段的可访问状态。
    • void setAccessible(boolean flag):设置字段的可访问状态。如果要访问 private 字段,需要将 flag 设置为 true
  5. 获取字段的注解:

    • Annotation[] getAnnotations():返回字段的注解。
    • Annotation[] getDeclaredAnnotations():返回字段的所有注解。
    • T getAnnotation(Class<T> annotationClass):返回字段的指定类型的注解。

Method

Method 类是 Java 反射中的一个重要类,用于表示类的方法。以下是 Method 类中的一些重要方法:

  1. 获取方法的名称和修饰符:

    • String getName():返回方法的名称。
    • int getModifiers():返回方法的修饰符,例如 public、private、protected、static 等。
  2. 获取方法的参数类型和返回类型:

    • Class<?>[] getParameterTypes():返回方法的参数类型。
    • Type[] getGenericParameterTypes():返回方法的泛型参数类型。
    • Class<?> getReturnType():返回方法的返回类型。
    • Type getGenericReturnType():返回方法的泛型返回类型。
  3. 获取方法的异常类型:

    • Class<?>[] getExceptionTypes():返回方法抛出的异常类型。
    • Type[] getGenericExceptionTypes():返回方法抛出的泛型异常类型。
  4. 调用方法:

    • Object invoke(Object obj, Object... args):调用指定对象上的该方法。obj 参数表示方法所在的对象,args 参数表示方法的参数值。对于静态方法,obj 参数可以为 null
  5. 获取和设置方法的访问权限:

    • boolean isAccessible():返回方法的可访问状态。
    • void setAccessible(boolean flag):设置方法的可访问状态。如果要访问 private 方法,需要将 flag 设置为 true
  6. 获取方法的注解:

    • Annotation[] getAnnotations():返回方法的注解。
    • Annotation[] getDeclaredAnnotations():返回方法的所有注解。
    • T getAnnotation(Class<T> annotationClass):返回方法的指定类型的注解。
    • Annotation[][] getParameterAnnotations():返回方法参数的注解。

创建实例

  1. 使用默认构造方法创建实例:
// 加载类
Class<?> cls = Class.forName("Reflection.bean.Person");

// 获取默认构造方法(无参构造方法)
Constructor<?> constructor = cls.getDeclaredConstructor();

// 设置构造方法的访问权限,如果构造方法是私有的,则需要设置为 true
constructor.setAccessible(true);

// 创建实例
Object obj = constructor.newInstance();
  1. 使用带参数的构造方法创建实例:
// 加载类
Class<?> cls = Class.forName("Reflection.bean.Person");

// 获取带参数的构造方法
Constructor<?> constructor = cls.getDeclaredConstructor(String.class, int.class);

// 设置构造方法的访问权限,如果构造方法是私有的,则需要设置为 true
constructor.setAccessible(true);

// 创建实例,传入构造方法的参数值
Object obj = constructor.newInstance("张三", 42);

Class 类:可以使用 Class 类的 newInstance 方法创建类的实例,但需要注意的是,这种方式仅适用于具有无参(默认)构造方法的类。在 Java 9 中,Class 类的 newInstance 方法已被弃用,推荐使用 Constructor 类的 newInstance 方法。

// 加载类
Class\<?> cls = Class.forName("Reflection.bean.Person");
// 使用无参构造方法创建实例
Object obj = cls.newInstance();

Class 类中的 newInstance 方法被弃用,原因是它存在一些问题和局限性。主要问题如下:

  1. 异常处理不明确:Class 类的 newInstance 方法只能抛出 InstantiationExceptionIllegalAccessException 异常。当调用构造方法时,如果构造方法本身抛出了异常,newInstance 会将该异常包装为 InvocationTargetException,然后将其设置为 InstantiationException 的原因(cause),这使得异常处理变得复杂和不直观。

  2. 仅支持无参构造方法:Class 类的 newInstance 方法只能调用类的无参(默认)构造方法。这对于需要使用带参数构造方法的类来说是不够灵活的。

为了解决这些问题,Java 引入了 Constructor 类的 newInstance 方法。它具有以下优势:

  1. 异常处理更清晰:Constructor 类的 newInstance 方法可以直接抛出 InvocationTargetException,使得异常处理更直接和简单。

  2. 支持带参数构造方法:Constructor 类的 newInstance 方法可以处理带参数的构造方法,使其具有更好>的灵活性。

  3. 支持访问控制:通过 Constructor 类的 setAccessible 方法,可以在必要时访问私有构造方法。

反射的常见面试题

  1. 什么是 Java 反射?

    反射是 Java 语言提供的一种机制,允许在运行时检查、访问和操作类、方法、字段和构造方法等元素。使用反射,可以在程序运行时动态加载类、创建对象、调用方法和访问字段。

  2. Java 反射的主要用途是什么?

    Java 反射的主要用途包括:

    • 动态加载类和创建对象
    • 动态调用方法
    • 动态访问和修改字段
    • 获取类的元数据(例如类名、方法、字段、注解等)
    • 实现动态代理
  3. 如何使用反射获取类的实例?

    使用 Class.forName("类的全限定名") 方法动态加载类,然后使用 Constructor.newInstance() 方法创建类的实例。

  4. 如何使用反射调用方法?

    使用 Class.getDeclaredMethod() 方法获取特定的方法,然后使用 Method.invoke() 方法调用该方法。

  5. 如何使用反射访问字段?

    使用 Class.getDeclaredField() 方法获取特定的字段,然后使用 Field.get()Field.set() 方法分别获取和设置字段的值。

  6. 反射的性能问题?

    反射操作相对于直接操作会有一定的性能损失,因为反射涉及到运行时类型检查、方法调用等额外操作。在性能敏感的场景下应谨慎使用反射。但在很多场景中,反射带来的灵活性和可扩展性可以抵消性能损失。

  7. 什么是动态代理?如何使用反射实现动态代理?

    动态代理是一种在运行时动态创建代理对象的技术,代理对象可以实现指定的接口。Java 反射提供了 java.lang.reflect.Proxy 类来实现动态代理。通过 Proxy.newProxyInstance() 方法可以创建代理对象,需要提供一个实现了 InvocationHandler 接口的对象来处理代理方法的调用。

转载自:https://juejin.cn/post/7223280402474926138
评论
请登录