反射 API 有什么用?

Java 的反射机制允许开发人员在运行时进行更改并获取有关类、接口、字段和方法的信息,而无需知道它们的名称。

反射 API 还允许您创建新对象、调用方法以及获取或设置字段值。

让我们列出使用反射可以做的所有事情:

  • 识别/确定对象的类别
  • 获取有关类修饰符、字段、方法、常量、构造函数和超类的信息
  • 找出哪些方法属于已实现的接口
  • 创建一个类的实例,其类名在程序执行之前是未知的
  • 按名称获取和设置实例字段的值
  • 按名称调用实例方法

几乎所有现代 Java 技术都使用反射。它是当今大多数 Java / Java EE 框架和库的基础,例如:

  • 用于构建 Web 应用程序的Spring框架
  • JUnit测试框架

如果您以前从未遇到过这些机制,您可能会问为什么所有这些都是必要的。答案很简单但也很模糊:反射极大地增加了灵活性以及定制我们的应用程序和代码的能力。

但总是有利有弊。那么让我们提几个缺点:

  • 违反应用程序安全性。反射让我们访问我们不应该访问的代码(违反封装)。
  • 安全限制。反射需要运行安全管理器的系统不可用的运行时权限。
  • 低性能。Java 中的反射通过扫描类路径以找到要加载的类来动态确定类型。这会降低程序的性能。
  • 维护困难。使用反射的代码难以阅读和调试。它不太灵活,也更难维护。

使用反射 API 处理类

所有反射操作都以java.lang.Class对象开始。对于每种类型的对象,都会创建一个不可变的java.lang.Class实例。它提供了获取对象属性、创建新对象和调用方法的方法。

让我们看一下使用java.lang.Class的基本方法列表:

方法 行动
字符串获取名称(); 返回类的名称
int getModifiers(); 返回访问修饰符
包 getPackage(); 返回有关包的信息
类 getSuperclass(); 返回有关父类的信息
类[] getInterfaces(); 返回一个接口数组
构造函数[] getConstructors(); 返回有关类构造函数的信息
字段[] getFields(); 返回类的字段
字段 getField(String fieldName); 按名称返回类的特定字段
方法[] getMethods(); 返回方法数组

这些是获取有关类、接口、字段和方法的数据的最重要的方法。还有一些方法可以让您获取或设置字段值,以及访问私有字段。我们稍后再看。

现在我们将讨论获取java.lang.Class本身。我们有三种方法可以做到这一点。

1.使用Class.forName

在正在运行的应用程序中,您必须使用forName(String className)方法来获取类。

此代码演示了我们如何使用反射创建类。让我们创建一个我们可以使用的Person类:


package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

我们示例的第二部分是使用反射的代码:


public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

如果已知类的全名,则可以使用此方法。然后你可以使用静态Class.forName()方法获取相应的类。此方法不能用于基本类型。

2. 使用.class

如果一个类型可用但没有它的实例,那么您可以通过将.class添加到类型名称来获取该类。这是获取原始类型类的最简单方法。


Class aClass = Person.class;

3. 使用.getClass()

如果对象可用,那么获取类的最简单方法是调用object.getClass()


Person person = new Person();
Class aClass = person.getClass();

最后两种方法有什么区别?

如果您在编码时知道您对哪个类对象感兴趣,请使用A.class 。如果没有实例可用,那么您应该使用.class

获取类的方法

让我们看看返回类方法的方法:getDeclaredMethods()getMethods()

getDeclaredMethods()返回一个数组,其中包含Class 对象表示的类或接口的所有已声明方法的Method对象,包括公共、私有、默认和受保护的方法,但不包括继承的方法。

getMethods()返回一个数组,其中包含由 Class 对象表示的类或接口的所有公共方法的Method对象——那些由类或接口声明的,以及那些从超类和超接口继承的。

让我们来看看它们各自是如何工作的。

让我们从getDeclaredMethods()开始。为了再次帮助我们理解这两种方法之间的区别,下面我们将使用抽象的Numbers类。让我们编写一个静态方法,将我们的Method数组转换为List<String>


import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

这是运行此代码的结果:

byteValue
shortValue
intValue
longValue
float floatValue;
双值

这些是在Number类中声明的方法。getMethods()返回什么?让我们更改示例中的两行:


final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

这样做,我们将看到以下一组方法:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

因为所有类都继承Object ,所以我们的方法也返回Object类的公共方法

获取类的字段

getFields和getDeclaredFields方法用于获取类的字段。例如,让我们看一下LocalDateTime类。我们将重写我们的代码:


import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

作为执行此代码的结果,我们获得了包含在 LocalDateTime 类中的字段集。

MIN
MAX
serialVersionUID
日期
时间

类比我们之前对方法的检查,让我们看看如果我们稍微更改代码会发生什么:


final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

输出:

最小
最大值

现在让我们弄清楚这些方法之间的区别。

getDeclaredFields方法返回一个Field对象数组,用于由 this 表示的类或接口声明的所有字段班级目的。

getFields方法为类或接口所代表的所有公共字段返回一个Field对象数组班级目的。

现在让我们看看LocalDateTime 的内部。

班级的最小值最大限度字段是公共的,这意味着它们将通过getFields方法可见。相比之下,日期,时间,serialVersionUID方法具有private修饰符,这意味着它们不会通过getFields方法可见,但我们可以使用getDeclaredFields获取它们。这就是我们如何访问私有字段的 Field 对象。

其他方法说明

现在该说说Class类的一些方法了,即:

方法 行动
获取修改器 为我们的班级获取修饰符
获取包裹 获取包含我们类的包
获取超类 获取父类
获取接口 获取类实现的接口数组
获取名称 获取完全限定的类名
获取简单名称 获取类名

getModifiers()

修饰符可以使用班级目的。

修饰符是关键字,如publicstaticinterface等。我们使用getModifiers()方法获取修饰符:


Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

此代码设置一个值整数位域变量。每个访问修饰符都可以通过设置或清除相应的位来打开或关闭。我们可以使用java.lang.reflect.Modifier类中的方法检查修饰符:


import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

回想一下我们的Person的声明是什么样的:


public class Person {
   …
}

我们得到以下输出:

类修饰符:1
是公共的:true
是静态的:false
是 final 的:false
是抽象的:false
是接口的:false

如果我们使我们的类抽象,那么我们有:


public abstract class Person { … }

这个输出:

类修饰符:1025
是公共的:true
是静态的:false
是最终的:false
是抽象的:true
是接口:false

我们更改了访问修饰符,这意味着我们还更改了通过Modifier类的静态方法返回的数据

获取包()

只知道一个类,我们可以得到关于它的包的信息:


Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

获取超类()

如果我们有一个 Class 对象,那么我们可以访问它的父类:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

我们得到众所周知的Object类:


class java.lang.Object

但是如果我们的类有另一个父类,那么我们会看到它:


package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

在这里我们得到我们的父类:


class com.company.Human

获取接口()

以下是我们如何获取类实现的接口列表的方法:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

我们不要忘记更改我们的Person类:


public class Person implements Serializable { … }

输出:

[接口 java.io.Serializable]

一个类可以实现很多接口。这就是为什么我们得到一个数组班级对象。在 Java Reflection API 中,接口也被表示为班级对象。

请注意:该方法只返回指定类实现的接口,不返回其父类。要获得该类实现的接口的完整列表,您需要引用当前类及其在继承链上游的所有祖先。

getName() & getSimpleName() & getCanonicalName()

让我们编写一个涉及原始类、嵌套类、匿名类和String类的示例:


public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

我们程序的结果:

int class (primitive):
getName(): int
getCanonicalName(): int
getSimpleName(): int
getTypeName(): int

String.class (普通类):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (嵌套类):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (匿名内部类):
getName() ): TestReflection$1
getCanonicalName()): 空
getSimpleName()):
getTypeName(): 测试反射 $1

现在让我们分析程序的输出:

  • getName()返回实体的名称。

  • getCanonicalName()返回基类的规范名称,如 Java 语言规范所定义。如果基类没有规范名称(即,如果它是本地类或匿名类或元素类型没有规范名称的数组),则返回 null。

  • getSimpleName()返回源代码中指定的基类的简单名称。如果基类是匿名的,则返回一个空字符串。

  • getTypeName()返回此类型名称的信息字符串。