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

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

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

答案

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

例如,假设您在 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 ++ 就是一个这样的例子。

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

例如,Java 中的所有对象都具有方法getClass() ,即使您在编译时不知道该对象的类(例如,如果您将其声明为Object ),也可以通过它来确定该对象的类 - 这似乎很简单,但是这样在不太动态的语言(例如C++反射是不可能的。更高级的用法使您可以列出和调用方法,构造函数等。

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

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

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

反射的用途

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

扩展功能

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

反思的缺点

反射功能强大,但不应任意使用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。通过反射访问代码时,应牢记以下注意事项。

  • 绩效开销

由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能比非反射操作慢,因此应避免在对性能敏感的应用程序中经常调用的代码段中。

  • 安全限制

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

  • 内部暴露

由于反射允许代码执行非反射代码中非法的操作(例如访问私有字段和方法),因此使用反射可能会导致意外的副作用,这可能会使代码无法正常工作并可能破坏可移植性。反射代码破坏了抽象,因此可能会随着平台的升级而改变行为。

来源: Reflection API

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

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

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

最后,最全面的示例是Spring ,它使用反射来创建其 bean,并大量使用代理

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

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

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

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

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,但是如果使用过多,它可能会使应用程序变慢,因为它可以在运行时解析所有类型。

例:

以一个远程应用程序为例,该应用程序为您的应用程序提供了一个对象,该对象是使用其 API 方法获得的。现在,基于对象,您可能需要执行某种计算。

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

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

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

一个简单的 Java 反射示例向您展示如何使用反射:

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

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

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

确切说明所有这些工作原理

编辑 :将近一年后,我正在编辑此答案,因为在阅读有关反射的文章时,我对反射的使用很少。

  • Spring 使用 bean 配置,例如:


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

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

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

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

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

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

}