题 什么是反思,为什么它有用?


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

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


1696
2017-09-01 08:39


起源


对我来说,这是一种在运行时获取类名并创建该类对象的方法。 - Nabin
因为这是一个很受欢迎的问题,我想指出反射(没有注释)应该是解决问题时最后的工具。我使用它并喜欢它,但它取消了Java静态类型的所有优点。如果确实需要它,请将其隔离到尽可能小的区域(一种方法或一种类)。在测试中使用它比生产代码更容易接受。使用注释应该没问题 - 主要的一点是如果你可以避免它,就不要将类或方法名称指定为“字符串”。 - Bill K
也可以看看: softwareengineering.stackexchange.com/questions/123956/... - givanse


答案:


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

例如,假设您在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 ++


1417
2017-09-01 08:44



你可以解释一下这个空参数在这一行中的意义是什么?方法方法= foo.getClass()。getMethod(“doSomething”,null); - Krsna Chaitanya
null表示没有参数传递给foo方法。看到 docs.oracle.com/javase/6/docs/api/java/lang/reflect/...,java.lang.Object ...)了解详细信息。 - Matt Sheppard
只是为了清理,因为这有很多赞成。检查系统中的代码并查看对象类型的能力不是反射,而是类型自省。然后反思是通过利用内省在运行时进行修改的能力。这里的区别是必要的,因为有些语言支持内省,但不支持反思。一个这样的例子是C ++。 - bigtunacan
我喜欢反射但是如果你能控制代码,那么使用这个答案中指定的反射是不合理的,因此是滥用 - 你应该使用Type Introspection(instanceof)和强类型。如果有任何办法,但反思做某事,那就是应该怎么做。反思导致严重的心痛,因为你失去了使用静态类型语言的所有优点。如果你需要它,你需要它,但是即便如此,我也会考虑像Spring这样的预先打包的解决方案或完全封装必要反射的东西 - IE:让其他人头痛。 - Bill K
@bigtunacan你从哪里获得这些信息?我在Oracle的官方Java文档中看到术语“反射”,不仅描述了在运行时进行更改的能力,还描述了查看对象类型的能力。更不用说大多数所谓的“类型内省”相关类(例如: Method, Constructor, Modifier, Field, Member,基本上显然都是除了 Class)是在...内 java.lang.*reflect* 包。也许“反思”这个概念在运行时全面包括“类型内省”和修改? - RestInPeace


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

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

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

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


195
2017-09-01 08:52





我最喜欢使用的反射之一是下面的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();
}

84
2017-09-02 16:15



Callcount应该设置为什么? - Tom
当我运行它时,我在线程“AWT-EventQueue-0”中遇到了一个异常java.lang.StackOverflowError。 - Tom
@汤姆 callCount 应设为零。它的值用于确定每行输出前应有多少个选项卡:每次转储需要转储“子对象”时,输出将打印为嵌套在父对象中。当包裹在另一个中时,该方法证明是有用的。考虑 printDump(Object obj){ System.out.println(dump(obj, 0)); }。 - fny
由于未经检查的递归,可能会在循环引用的情况下创建java.lang.StackOverflowError:buffer.append(dump(value,callCount)) - Arnaud P
你可以专门将你的代码发布到Public Domain吗? - stolsvik


反思的使用

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

可扩展性功能

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

反思的缺点

反思是强大的,但不应滥用。如果可以在不使用反射的情况下执行操作,则优选避免使用它。通过反射访问代码时,应牢记以下问题。

  • 绩效开销

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

  • 安全限制

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

  • 内部接触

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

资源: Reflection API


56
2017-10-17 11:59





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

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

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

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


30
2017-09-01 09:30





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

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

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


27
2017-09-01 08:50



我需要根据DB中的数据实例化对象。我相信这就是你所说的。示例代码对我有很大帮助。提前致谢。 - Atom


Reflection允许在运行时动态地实例化新对象,调用方法和对类变量进行get / set操作,而无需事先了解其实现。

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。

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

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

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


26
2017-07-08 16:12



这是在答案部分帮助我最多的帖子,谢谢Nikhil - Enoy


示例:
以远程应用程序为例,它为您的应用程序提供了一个使用API​​方法获得的对象。现在基于对象,您可能需要执行某种计算。
提供者保证对象可以是3种类型,我们需要根据对象的类型执行计算。
因此,我们可以在3个类中实现,每个类包含不同的逻辑。显然,对象信息在运行时可用,因此您无法静态编码以执行计算,因此反射用于实例化基于此类所需执行计算的类的对象。从提供者处收到的对象。


18
2018-06-22 15:35



我需要类似的东西......一个例子对我很有帮助,因为我是反思概念的新手。 - Atom
我很困惑:你不能用 instanceof 在运行时确定对象类型? - ndm13


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

一个快速的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>元素时,它将使用Class.forName(String)和参数“com.example.Foo”来实例化该Class。

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

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

对于私人方法,

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);

17
2017-09-08 09:40





据我了解:

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

它经常用于类名经常更改的场景中。如果出现这种情况,那么程序员重写应用程序并一次又一次地更改类的名称是很复杂的。

相反,通过使用反射,需要担心可能更改的类名。


12
2018-02-06 05:37