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

时间:2008-09-01 08:39:22

标签: java reflection terminology

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

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

22 个答案:

答案 0 :(得分:1577)

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

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

答案 1 :(得分:222)

  

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

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

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

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

答案 2 :(得分:102)

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

答案 3 :(得分:72)

反思的使用

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

可扩展性功能

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

反思的缺点

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

  • 绩效开销

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

  • 安全限制

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

  • 内部曝光

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

来源:The Reflection API

答案 4 :(得分:39)

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

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

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

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

答案 5 :(得分:34)

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

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

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

答案 6 :(得分:32)

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,但如果过度使用它可能会降低应用程序的速度,因为它会在运行时解析所有类型。

答案 7 :(得分:21)

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

答案 8 :(得分:20)

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

快速Java反射示例,向您展示使用反射的内容:

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

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

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

Exactly how all this works is explained here

编辑:差不多1年后,我正在编辑这个答案,因为在阅读反思的过程中,我得到了更多的反射用途。

  • Spring使用bean配置,例如:


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

当Spring上下文处理这个&lt;豆>在元素中,它将使用Class.forName(String)和参数&#34; com.example.Foo&#34;实例化该类。

然后它将再次使用反射来获得&lt;的适当的setter。物业&gt; element并将其值设置为指定值。

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

答案 9 :(得分:15)

根据我的理解:

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

经常在类名经常更改的情况下使用。如果出现这种情况,那么程序员重写应用程序并一次又一次地更改类的名称就很复杂。

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

答案 10 :(得分:15)

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

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

    反射可用于获取有关 -

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

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

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

    4. Reflection API 主要用于:

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

      使用反思的优势:

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

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

      <强>缺点:

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

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

      参考:Java Reflection javarevisited.blogspot.in

答案 11 :(得分:14)

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

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

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

它在哪里有用?每次都没有用,但具体情况。例如,您可以使用它来获取用于登录目的的类的名称,以根据配置文件中指定的内容为dinamically创建事件的处理程序,等等......

答案 12 :(得分:14)

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

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

}

答案 13 :(得分:11)

反思是让对象看到它们的外观。这个论点似乎与反思无关。事实上,这就是&#34;自我识别&#34;能力。

对于像Java和C#这样缺乏自我知识和自我感知能力的语言来说,反思本身就是一个词。因为他们没有自我知识的能力,当我们想要观察它的样子时,我们必须有另一件事来反思它的样子。优秀的动态语言(如Ruby和Python)可以在没有其他人帮助的情况下感知自己的反映。我们可以说Java的对象无法在没有镜像的情况下看到它的样子,镜像是反射类的对象,但Python中的对象可以在没有镜像的情况下感知它。这就是我们需要在Java中进行反思的原因。

答案 14 :(得分:9)

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

使用 Reflection 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;
   }    
}

答案 15 :(得分:9)

正如名称本身所暗示的那样,除了提供调用在运行时动态创建实例的方法的功能外,它还反映了它对示例类方法等的作用。

许多框架和应用程序使用它来调用服务而不需要实际知道代码。

答案 16 :(得分:9)

来自java文档page

  

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

AccessibleObject允许在必要的ReflectPermission可用时禁止访问检查。

此包中的类以及java.lang.Class适用于调试器,解释器,对象检查器,类浏览器以及需要访问其中的Object SerializationJavaBeans等服务的应用程序。目标对象的公共成员(基于其运行时类)或由给定类声明的成员

它包括以下功能。

  1. 获取类对象,
  2. 检查类的属性(字段,方法,构造函数),
  3. 设置和获取字段值
  4. 调用方法,
  5. 创建新的对象实例。
  6. 请查看 Class 类公开的方法的documentation链接。

    来自article(Sosnoski Software Solutions,Inc总裁Dennis Sosnoski)和article(安全探索pdf):

    我可以看到使用Reflection

    的相当大的缺点

    反思用户:

    1. 它提供了动态链接程序组件的多种方式
    2. 对于创建以非常通用的方式使用对象的库非常有用
    3. 反思的缺点:

      1. 当用于字段和方法访问时,反射比直接代码慢得多。
      2. 它可以掩盖代码中实际发生的事情
      3. 绕过源代码可能会造成维护问题
      4. 反射代码也比相应的直接代码
      5. 更复杂
      6. 它允许违反关键的Java安全约束 作为数据访问保护和类型安全
      7. 一般滥用行为:

        1. 加载限制类,
        2. 获取对受限类的构造函数,方法或字段的引用,
        3. 创建新对象实例,方法调用,获取或设置受限类的字段值。
        4. 看看有关滥用反射功能的SE问题:

          How do I read a private field in Java?

          <强>要点:

          不安全地使用从系统代码中执行的功能也很容易导致Java安全模式的危害 l。 请谨慎使用此功能

答案 17 :(得分:6)

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

答案 18 :(得分:6)

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

  

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

答案 19 :(得分: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());

因为在这种情况下很难产生拼写错误。

答案 20 :(得分:1)

我发现最好通过示例进行解释,而答案似乎都没有做到这一点……

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

答案 21 :(得分:0)

重要

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

默认情况下,拒绝对模块中所有软件包的反射访问。

请参见Understanding Java 9 Modules