2394

什么是反射,它为什么有用?

我特别感兴趣Java,但我认为原则在任何语言中都是相同的。

4

23 回答 23

1919

名称反射用于描述能够检查同一系统(或自身)中的其他代码的代码。

例如,假设您在 Java 中有一个未知类型的对象,如果存在,您想对其调用“doSomething”方法。除非对象符合已知接口,否则 Java 的静态类型系统并非真正设计为支持这一点,但是使用反射,您的代码可以查看对象并确定它是否有一个名为“doSomething”的方法,然后调用它,如果你想要。

所以,给你一个 Java 中的代码示例(想象有问题的对象是 foo):

Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);

Java 中一个非常常见的用例是使用注释。例如,JUnit 4 将使用反射在您的类中查看带有 @Test 注释标记的方法,然后在运行单元测试时调用它们。

有一些很好的反射示例可以帮助您入门http://docs.oracle.com/javase/tutorial/reflect/index.html

最后,是的,这些概念在其他支持反射的静态类型语言(如 C#)中非常相似。在动态类型语言中,上面描述的用例不太必要(因为编译器将允许在任何对象上调用任何方法,如果它不存在则在运行时失败),但第二种情况是寻找标记或以某种方式工作仍然很普遍。

从评论更新:

检查系统中的代码并查看对象类型的能力不是反射,而是类型自省。然后,反射是通过使用自省在运行时进行修改的能力。这里有必要进行区分,因为某些语言支持自省,但不支持反射。一个这样的例子是 C++

于 2008-09-01T08:44:58.657 回答
281

反射是一种语言在运行时检查和动态调用类、方法、属性等的能力。

例如,Java 中的所有对象都有 method getClass(),即使您在编译时不知道它(例如,如果您将其声明为Object),它也可以让您确定对象的类 - 这似乎微不足道,但这种反射是不可能的在动态较少的语言中,例如C++. 更高级的用途可以让您列出和调用方法、构造函数等。

反射很重要,因为它允许您编写不必在编译时“了解”所有内容的程序,从而使它们更具动态性,因为它们可以在运行时绑定在一起。可以针对已知接口编写代码,但要使用的实际类可以使用配置文件中的反射来实例化。

由于这个原因,许多现代框架广泛使用反射。大多数其他现代语言也使用反射,并且在脚本语言(例如 Python)中它们的集成更加紧密,因为在这些语言的通用编程模型中感觉更加自然。

于 2008-09-01T08:52:29.400 回答
123

我最喜欢的反射用途之一是下面的 Java 转储方法。它接受任何对象作为参数,并使用 Java 反射 API 打印出每个字段名称和值。

import java.lang.reflect.Array;
import java.lang.reflect.Field;

public static String dump(Object o, int callCount) {
    callCount++;
    StringBuffer tabs = new StringBuffer();
    for (int k = 0; k < callCount; k++) {
        tabs.append("\t");
    }
    StringBuffer buffer = new StringBuffer();
    Class oClass = o.getClass();
    if (oClass.isArray()) {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("[");
        for (int i = 0; i < Array.getLength(o); i++) {
            if (i < 0)
                buffer.append(",");
            Object value = Array.get(o, i);
            if (value.getClass().isPrimitive() ||
                    value.getClass() == java.lang.Long.class ||
                    value.getClass() == java.lang.String.class ||
                    value.getClass() == java.lang.Integer.class ||
                    value.getClass() == java.lang.Boolean.class
                    ) {
                buffer.append(value);
            } else {
                buffer.append(dump(value, callCount));
            }
        }
        buffer.append(tabs.toString());
        buffer.append("]\n");
    } else {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("{\n");
        while (oClass != null) {
            Field[] fields = oClass.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                buffer.append(tabs.toString());
                fields[i].setAccessible(true);
                buffer.append(fields[i].getName());
                buffer.append("=");
                try {
                    Object value = fields[i].get(o);
                    if (value != null) {
                        if (value.getClass().isPrimitive() ||
                                value.getClass() == java.lang.Long.class ||
                                value.getClass() == java.lang.String.class ||
                                value.getClass() == java.lang.Integer.class ||
                                value.getClass() == java.lang.Boolean.class
                                ) {
                            buffer.append(value);
                        } else {
                            buffer.append(dump(value, callCount));
                        }
                    }
                } catch (IllegalAccessException e) {
                    buffer.append(e.getMessage());
                }
                buffer.append("\n");
            }
            oClass = oClass.getSuperclass();
        }
        buffer.append(tabs.toString());
        buffer.append("}\n");
    }
    return buffer.toString();
}
于 2008-09-02T16:15:28.813 回答
93

反射的用途

反射通常由需要能够检查或修改在 Java 虚拟机中运行的应用程序的运行时行为的程序使用。这是一个相对高级的特性,只应该由对语言基础有深入了解的开发人员使用。考虑到这一点,反射是一种强大的技术,可以使应用程序执行原本不可能的操作。

可扩展性功能

应用程序可以通过使用其完全限定名称创建可扩展性对象的实例来使用外部的、用户定义的类。类浏览器和可视化开发环境 类浏览器需要能够枚举类的成员。可视化开发环境可以受益于利用反射中可用的类型信息来帮助开发人员编写正确的代码。调试器和测试工具 调试器需要能够检查类中的私有成员。测试工具可以利用反射系统地调用定义在类上的可发现集 API,以确保测试套件中的高水平代码覆盖率。

反射的缺点

反射很强大,但不应该乱用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。通过反射访问代码时应牢记以下问题。

  • 性能开销

由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能比它们的非反射对应物要慢,并且应该避免在对性能敏感的应用程序中频繁调用的代码部分中。

  • 安全限制

反射需要在安全管理器下运行时可能不存在的运行时权限。对于必须在受限安全上下文中运行的代码(例如在 Applet 中),这是一个重要的考虑因素。

  • 内部暴露

由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,因此使用反射可能会导致意想不到的副作用,这可能会使代码功能失调并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

来源:反射 API

于 2014-10-17T11:59:33.803 回答
49

反射是允许应用程序或框架处理可能尚未编写的代码的关键机制!

以典型的 web.xml 文件为例。这将包含一个 servlet 元素列表,其中包含嵌套的 servlet 类元素。servlet 容器将处理 web.xml 文件,并通过反射为每个 servlet 类创建一个新实例。

另一个例子是 Java API for XML Parsing (JAXP)。XML 解析器提供程序通过众所周知的系统属性“插入”,这些属性用于通过反射构造新实例。

最后,最全面的例子是Spring,它使用反射来创建它的 bean,以及大量使用代理

于 2008-09-01T09:30:55.270 回答
46

并非每种语言都支持反射,但支持反射的语言中的原理通常是相同的。

反思是“反思”程序结构的能力。或者更具体。查看您拥有的对象和类,并以编程方式获取有关它们实现的方法、字段和接口的信息。您还可以查看注释等内容。

它在很多情况下都很有用。您希望能够在代码中动态插入类的任何地方。许多对象关系映射器使用反射来实例化数据库中的对象,而无需事先知道它们将使用什么对象。插件架构是反射有用的另一个地方。在这些情况下,能够动态加载代码并确定是否存在实现正确接口以用作插件的类型很重要。

于 2008-09-01T08:50:57.813 回答
39

反射允许在运行时动态地实例化新对象、调用方法以及对类变量进行获取/设置操作,而无需事先了解其实现。

Class myObjectClass = MyObject.class;
Method[] method = myObjectClass.getMethods();

//Here the method takes a string parameter if there is no param, put null.
Method method = aClass.getMethod("method_name", String.class); 

Object returnValue = method.invoke(null, "parameter-value1");

在上面的示例中,null 参数是您要调用该方法的对象。如果方法是静态的,则提供 null。如果该方法不是静态的,那么在调用时您需要提供一个有效的 MyObject 实例而不是 null。

反射还允许您访问类的私有成员/方法:

public class A{

  private String str= null;

  public A(String str) {
  this.str= str;
  }
}

.

A obj= new A("Some value");

Field privateStringField = A.class.getDeclaredField("privateString");

//Turn off access check for this field
privateStringField.setAccessible(true);

String fieldValue = (String) privateStringField.get(obj);
System.out.println("fieldValue = " + fieldValue);
  • 对于类的检查(也称为自省),您不需要导入反射包 ( java.lang.reflect)。可以通过 访问类元数据java.lang.Class

反射是一个非常强大的 API,但如果过度使用它可能会减慢应用程序的速度,因为它会在运行时解析所有类型。

于 2013-07-08T16:12:04.037 回答
26

Java 反射非常强大并且非常有用。Java 反射可以在运行时检查类、接口、字段和方法,而无需在编译时知道类、方法等的名称。也可以使用反射实例化新对象、调用方法和获取/设置字段值。

一个快速的 Java 反射示例,向您展示如何使用反射:

Method[] methods = MyObject.class.getMethods();

    for(Method method : methods){
        System.out.println("method = " + method.getName());
    }

此示例从名为 MyObject 的类中获取 Class 对象。该示例使用类对象获取该类中的方法列表,迭代方法并打印出它们的名称。

这里解释了所有这些工作的确切方式

编辑:将近 1 年后,我正在编辑这个答案,因为在阅读反射时,我对反射有了更多的使用。

  • Spring 使用 bean 配置,例如:


<bean id="someID" class="com.example.Foo">
    <property name="someField" value="someValue" />
</bean>

当 Spring 上下文处理这个 <bean> 元素时,它将使用带有参数“com.example.Foo”的 Class.forName(String) 来实例化该类。

然后它将再次使用反射来为 <property> 元素获取适当的设置器并将其值设置为指定值。

  • Junit 特别使用反射来测试私有/受保护的方法。

对于私有方法,

Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

对于私有领域,

Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
于 2014-09-08T09:40:02.520 回答
23

例子:

以一个远程应用程序为例,它为您的应用程序提供一个您使用其 API Methods 获得的对象。现在根据您可能需要执行某种计算的对象。

提供者保证 object 可以是 3 种类型,我们需要根据 object 的类型进行计算。

因此,我们可能会在 3 个类中实现,每个类都包含不同的逻辑。显然,对象信息在运行时可用,因此您不能静态编码来执行计算,因此反射用于实例化您需要基于从提供者收到的对象。

于 2012-06-22T15:35:55.883 回答
22

反射的简单示例。在国际象棋游戏中,您不知道用户在运行时会移动什么。反射可用于调用已在运行时实现的方法:

public class Test {

    public void firstMoveChoice(){
        System.out.println("First Move");
    } 
    public void secondMOveChoice(){
        System.out.println("Second Move");
    }
    public void thirdMoveChoice(){
        System.out.println("Third Move");
    }

    public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { 
        Test test = new Test();
        Method[] method = test.getClass().getMethods();
        //firstMoveChoice
        method[0].invoke(test, null);
        //secondMoveChoice
        method[1].invoke(test, null);
        //thirdMoveChoice
        method[2].invoke(test, null);
    }

}
于 2014-11-06T04:42:31.100 回答
19

反射是一种 API,用于在运行时检查或修改方法、类、接口的行为。

  1. 反射所需的类在下面提供java.lang.reflect package
  2. 反射为我们提供了有关对象所属的类的信息,以及可以使用该对象执行的该类的方法。
  3. 通过反射,我们可以在运行时调用方法,而不管它们使用的访问说明符如何。

和包为java反射提供类java.langjava.lang.reflect

反射可用于获取有关 –</p> 的信息

  1. ClassgetClass()方法用于获取对象所属的类的名称。

  2. ConstructorsgetConstructors()方法用于获取对象所属类的公共构造函数。

  3. 方法getMethods()方法用于获取对象所属类的公共方法。

反射 API主要用于:

IDE(集成开发环境),例如 Eclipse、MyEclipse、NetBeans 等。
调试器和测试工具等。

使用反射的优点:

可扩展性特性:应用程序可以通过使用它们的完全限定名称创建可扩展性对象的实例来使用外部的、用户定义的类。

调试和测试工具:调试器使用反射属性来检查类的私有成员。

缺点:

性能开销:反射操作的性能比非反射操作慢,应避免在对性能敏感的应用程序中频繁调用的代码段中使用。

内部暴露:反射代码破坏了抽象,因此可能会随着平台的升级而改变行为。

参考:Java 反射 javarevisited.blogspot.in

于 2017-03-22T07:27:08.683 回答
17

反射是一组函数,允许您访问程序的运行时信息并修改它的行为(有一些限制)。

它很有用,因为它允许您根据程序的元信息更改运行时行为,也就是说,您可以检查函数的返回类型并更改处理情况的方式。

例如,在 C# 中,您可以在运行时加载程序集(.dll)并检查它,在类中导航并根据您找到的内容采取行动。它还允许您在运行时创建类的实例、调用其方法等。

它在哪里有用?并非每次都有用,但适用于具体情况。例如,您可以使用它来获取类的名称以进行日志记录,根据配置文件中指定的内容动态创建事件处理程序等等......

于 2008-09-01T08:50:52.260 回答
17

据我了解:

反射允许程序员动态地访问程序中的实体。即,在编写应用程序时,如果程序员不知道类或其方法,他可以通过使用反射动态地(在运行时)使用此类。

常用于类名频繁变化的场景。如果出现这种情况,那么对于程序员来说,重写应用程序并一次又一次地更改类的名称是很复杂的。

相反,通过使用反射,需要担心类名可能会发生变化。

于 2012-02-06T05:37:25.890 回答
12

我只想为列出的所有内容添加一些要点。

使用反射 API,您可以为任何对象编写通用toString()方法。

它在调试时很有用。

下面是一些例子:

class ObjectAnalyzer {

   private ArrayList<Object> visited = new ArrayList<Object>();

   /**
    * Converts an object to a string representation that lists all fields.
    * @param obj an object
    * @return a string with the object's class name and all field names and
    * values
    */
   public String toString(Object obj) {
      if (obj == null) return "null";
      if (visited.contains(obj)) return "...";
      visited.add(obj);
      Class cl = obj.getClass();
      if (cl == String.class) return (String) obj;
      if (cl.isArray()) {
         String r = cl.getComponentType() + "[]{";
         for (int i = 0; i < Array.getLength(obj); i++) {
            if (i > 0) r += ",";
            Object val = Array.get(obj, i);
            if (cl.getComponentType().isPrimitive()) r += val;
            else r += toString(val);
         }
         return r + "}";
      }

      String r = cl.getName();
      // inspect the fields of this class and all superclasses
      do {
         r += "[";
         Field[] fields = cl.getDeclaredFields();
         AccessibleObject.setAccessible(fields, true);
         // get the names and values of all fields
         for (Field f : fields) {
            if (!Modifier.isStatic(f.getModifiers())) {
               if (!r.endsWith("[")) r += ",";
               r += f.getName() + "=";
               try {
                  Class t = f.getType();
                  Object val = f.get(obj);
                  if (t.isPrimitive()) r += val;
                  else r += toString(val);
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }
         }
         r += "]";
         cl = cl.getSuperclass();
      } while (cl != null);

      return r;
   }    
}
于 2014-09-20T22:22:03.837 回答
12

反射就是让物体看到自己的样子。这个论点似乎与反思无关。其实,这就是“自我认同”的能力。

反射本身就是Java、C#等缺乏自知自知能力的语言的代名词。因为他们没有自知之明的能力,所以当我们要观察它的样子时,我们必须有另外的东西去反省它的样子。优秀的动态语言如 Ruby 和 Python 可以感知自己的反射,而无需其他人的帮助。可以说,Java 的对象没有镜子是无法感知它的样子,这是反射类的对象,但是 Python 中的对象可以在没有镜子的情况下感知它。这就是为什么我们需要在 Java 中进行反射。

于 2015-12-08T07:02:19.810 回答
9

从 java 文档页面

java.lang.reflect包提供类和接口,用于获取有关类和对象的反射信息。反射允许以编程方式访问有关已加载类的字段、方法和构造函数的信息,并允许在安全限制内使用反射字段、方法和构造函数对其底层对应物进行操作。

AccessibleObject如果有必要,允许禁止访问检查ReflectPermission

此包中的类,以及java.lang.Class调试器、解释器、对象检查器、类浏览器和服务等Object Serialization需要JavaBeans访问目标对象的公共成员(基于其运行时类)或由给定的类

它包括以下功能。

  1. 获取类对象,
  2. 检查类的属性(字段、方法、构造函数),
  3. 设置和获取字段值,
  4. 调用方法,
  5. 创建对象的新实例。

查看此文档链接以了解类公开的方法Class

从这篇文章(由 Sosnoski Software Solutions, Inc 总裁 Dennis Sosnoski 撰写)和这篇文章(security-explorations pdf):

与使用反射相比,我可以看到相当多的缺点

反射用户:

  1. 它提供了非常通用的方式来动态链接程序组件
  2. 它对于创建以非常通用的方式处理对象的库很有用

反射的缺点:

  1. 当用于字段和方法访问时,反射比直接代码慢得多。
  2. 它可以掩盖代码中实际发生的事情
  3. 它绕过源代码会产生维护问题
  4. 反射代码也比对应的直接代码复杂
  5. 它允许违反关键的 Java 安全约束,例如数据访问保护和类型安全

一般滥用:

  1. 加载受限类,
  2. 获取对受限类的构造函数、方法或字段的引用,
  3. 创建新对象实例、方法调用、获取或设置受限类的字段值。

看看这个关于滥用反射功能的 SE 问题:

如何读取 Java 中的私有字段?

概括:

在系统代码中不安全地使用其功能也很容易导致 Java 安全模式l 的妥协。因此,请谨慎使用此功能

于 2016-02-13T12:33:45.443 回答
9

顾名思义,它反映了它所拥有的例如类方法等,除了提供在运行时动态调用创建实例的方法的功能。

许多框架和应用程序都使用它来调用服务,而无需真正了解代码。

于 2017-04-09T02:29:34.620 回答
7

反射使您能够编写更通用的代码。它允许您在运行时创建对象并在运行时调用其方法。因此程序可以高度参数化。它还允许内省对象和类以检测其暴露于外部世界的变量和方法。

于 2016-04-26T22:54:49.010 回答
6

Reflection有很多用途。我更熟悉的是能够动态创建代码。

IE:动态类、函数、构造函数——基于任何数据(xml/array/sql results/hardcoded/etc..)

于 2013-05-06T20:16:28.380 回答
3

我想通过例子来回答这个问题。首先,Hibernate项目使用Reflection API生成CRUD语句来弥合正在运行的应用程序和持久性存储之间的鸿沟。当域中的事物发生变化时,Hibernate必须了解它们才能将它们持久保存到数据存储中,反之亦然。

或者工作Lombok Project。它只是在编译时注入代码,导致代码被插入到您的域类中。(我认为 getter 和 setter 都可以)

Hibernate之所以选择reflection它,是因为它对应用程序的构建过程影响最小。

从 Java 7 开始MethodHandles,我们有了Reflection API. 在项目中,要使用记录器,我们只需复制粘贴以下代码:

Logger LOGGER = Logger.getLogger(MethodHandles.lookup().lookupClass().getName());

因为在这种情况下很难出现拼写错误。

于 2018-12-05T06:35:57.470 回答
3

我发现最好通过示例来解释,但似乎没有一个答案能做到这一点......

使用反射的一个实际示例是用 Java 编写的 Java 语言服务器或用 PHP 编写的 PHP 语言服务器等。语言服务器为您的 IDE 提供自动完成、跳转到定义、上下文帮助、提示类型等功能。为了让所有标签名称(可以自动补全的单词)在您键入时显示所有可能的匹配项,Language Server 必须检查有关该类的所有内容,包括文档块和私有成员。为此,它需要反映所述类。

一个不同的例子是私有方法的单元测试。这样做的一种方法是创建反射并在测试的设置阶段将方法的范围更改为公共。当然,人们可能会争辩说不应该直接测试私有方法,但这不是重点。

于 2019-04-26T09:34:35.713 回答
1

我正在使用反射基于类名(字符串中的类名)创建一个对象并调用该类的方法

Object obj = Class.forName(config.getClassPath())
                    .getDeclaredConstructor()
                    .newInstance();
Method method = obj.getClass().getMethod("getCustomer", SearchObject.class, ObjectConfig.class,
                HttpServletRequest.class);
method.invoke(obj, searchObject, config, request);

但一个主要问题是,如果你在那个类上自动装配了一些东西,它将重新初始化为 null

于 2021-12-27T10:52:05.510 回答
0

重要的

从 Java 9 开始,您不能再使用反射,除非 package-info.java将模块打开以进行反射访问。

默认情况下,模块中的所有包都拒绝“反射”访问。

请参阅了解 Java 9 模块

于 2020-11-23T23:01:52.933 回答