关于Java:什么是反射reflection ,为什么它是有用的?

What is reflection and why is it useful?

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

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


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

例如,假设Java中有一个未知类型的对象,如果存在的话,您希望调用它的"doOffice"方法。Java的静态打字系统并不是真正设计来支持这个的,除非对象符合一个已知的接口,但是使用反射,你的代码可以查看对象并找出它是否有一个叫做"DoMeOffice"的方法,然后调用它。

所以,在Java中给你一个代码例子(想象一下这个对象是FO):

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

Java中一个非常常见的用例是注释的用法。例如,JUnit4将使用反射在类中查找带有@test注释的方法,然后在运行单元测试时调用这些方法。

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

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

从注释更新:

The ability to inspect the code in the system and see object types is
not reflection, but rather Type Introspection. Reflection is then the
ability to make modifications at runtime by making use of
introspection. The distinction is necessary here as some languages
support introspection, but do not support reflection. One such example
is C++


Reflection is a language's ability to inspect and dynamically call classes, methods, attributes, etc. at runtime.

例如,Java中的所有对象都有EDOCX1 0的方法,它可以让您确定对象的类,即使您在编译时不知道它(例如,如果您将其声明为EDCOX1(1)),这可能看起来微不足道,但是这种反射在诸如EDCOX1×2的不太动态的语言中是不可能的。更高级的用法允许您列出和调用方法、构造函数等。

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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("
"
);
        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("]
"
);
    } else {
        buffer.append("
"
);
        buffer.append(tabs.toString());
        buffer.append("{
"
);
        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("
"
);
            }
            oClass = oClass.getSuperclass();
        }
        buffer.append(tabs.toString());
        buffer.append("}
"
);
    }
    return buffer.toString();
}


反射的使用

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

可扩展性特征

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

反射的缺点

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

  • 性能开销

因为反射涉及动态分解的类型,所以无法执行某些Java虚拟机优化。因此,反射操作的性能比不反射操作的性能要慢,应该避免在性能敏感应用程序中经常调用的代码部分使用反射操作。

  • 安全限制

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

  • 内部暴露

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

来源:反射API


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

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

另一个例子是Java API的XML解析(JAXP)。其中,XML解析器提供程序通过已知的系统属性"插入",该属性用于通过反射构造新实例。

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


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

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

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


反射允许在运行时动态地实例化新对象、调用方法和对类变量执行get/set操作,而不必事先了解其实现。

1
2
3
4
5
6
7
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");

在上面的示例中,空参数是要在其上调用方法的对象。如果方法是静态的,则提供空值。如果方法不是静态的,那么在调用时需要提供有效的myObject实例,而不是空的。

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

1
2
3
4
5
6
7
8
public class A{

  private String str= null;

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

.

1
2
3
4
5
6
7
8
9
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反射非常强大,非常有用。Java反射使得可以在运行时检查类、接口、字段和方法,而不知道编译时类、方法等的名称。还可以使用反射来实例化新对象、调用方法和获取/设置字段值。

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

1
2
3
4
5
Method[] methods = MyObject.class.getMethods();

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

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

这里详细解释了这一切的工作原理。

编辑:在将近一年之后,我在编辑这个答案,因为在阅读关于反射的文章时,我很少再使用反射。

  • Spring使用bean配置,例如:
1
2
3
<bean id="someID" class="com.example.Foo">
    <property name="someField" value="someValue" />
</bean>

当Spring上下文处理这个元素时,它将使用class.forname(string)和参数"com.example.foo"来实例化该类。

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

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

对于私有方法,

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

对于私有领域,

1
2
3
Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);


根据我的理解:

反射允许程序员动态访问程序中的实体。也就是说,在编写应用程序时,如果程序员不知道某个类或其方法,他可以使用反射动态地(在运行时)使用该类。

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

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


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

  • 反射所需的类在java.lang.reflect package下提供。
  • 反射向我们提供了关于对象所属的类的信息,以及可以使用该对象执行的该类的方法。
  • 通过反射,我们可以在运行时调用方法,而不考虑与它们一起使用的访问说明符。
  • EDCOX1 1和EDCOX1 2个包为Java反射提供了类。

    反射可用于获取有关-

  • 类使用getClass()方法获取对象所属类的名称。

  • constructors使用getConstructors()方法获取对象所属类的公共构造函数。

  • 方法使用getMethods()方法获取对象所属类的公共方法。

  • 反射API主要用于:

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

    ></P><P>使用反射的优点:</P><P>可扩展性特性:应用程序可以通过使用扩展性对象的完全限定名创建扩展性对象的实例来使用外部的、用户定义的类。</P><P>调试和测试工具:调试器使用反射属性来检查类上的私有成员。</P><P>缺点:</P><P>性能开销:反射操作的性能比不反射操作的性能慢,应该避免在性能敏感应用程序中经常调用的代码部分使用。</P><P>内部暴露:反射代码破坏了抽象,因此可能随着平台的升级而改变行为。</P><P>参考文献:Java反射javaReavig.BogStut.in</P></p>
<div class=


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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);
        }

    }

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

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

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

    在哪里有用?不是每次都有用,而是针对具体情况。例如,您可以使用它获取用于loggin目的的类的名称,根据配置文件上指定的内容,常规地为事件创建处理程序,等等…


    反射就是让物体看到它们的外观。这个论点似乎与反思毫无关系。实际上,这就是"自我识别"能力。

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


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

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

    它对调试很有用。

    下面是一些例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    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;
       }    
    }

    从Java文档页面

    java.lang.reflect package provides classes and interfaces for obtaining reflective information about classes and objects. Reflection allows programmatic access to information about the fields, methods and constructors of loaded classes, and the use of reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.

    如果必要的ReflectPermission可用,AccessibleObject允许禁止访问检查。

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

    它包括以下功能。

  • 获取类对象,
  • 正在检查类的属性(字段、方法、构造函数),
  • 设置和获取字段值,
  • 调用方法,
  • 创建对象的新实例。
  • 查看此文档链接,了解Class类公开的方法。

    本文(作者Dennis Sosnoski,Sosnoski Software Solutions,Inc.总裁)和本文(Security Explorations PDF):

    比起使用反射,我可以看到很多缺点。

    反射用户:

  • 它提供了动态链接程序组件的非常通用的方法
  • 它对于创建以非常普通的方式处理对象的库很有用
  • 反射的缺点:

  • 当用于字段和方法访问时,反射比直接代码慢得多。
  • 它可以掩盖代码中实际发生的事情
  • 它绕过源代码会产生维护问题
  • 反射代码也比相应的直接代码更复杂
  • 它允许违反关键Java安全约束。AS数据访问保护和类型安全
  • 一般虐待:

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

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

    总结:

    从系统代码中不安全地使用其功能也很容易导致Java安全模型的折衷。所以要谨慎使用这个特性


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

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


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


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

    IE: dynamic classes, functions, constructors - based on any data
    (xml/array/sql results/hardcoded/etc..)


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

    或者工作于Lombok Project。它只是在编译时注入代码,导致代码被插入到域类中。(我认为对getter和setter来说没问题)

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

    从Java 7中,我们得到了EDOCX1,13,它作为EDCOX1,7。在项目中,要使用记录器,只需复制粘贴下一个代码:

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

    因为在这种情况下很难出错。


    我觉得最好用例子来解释,但似乎没有一个答案能做到这一点……

    使用反射的一个实际例子是Java语言服务器(简称Ls),它是用Java编写的。LS为您提供了诸如"自动完成"和"跳到定义"之类的IDE功能。为了让所有的标记名(可以自动完成的单词)在您按下时匹配,例如,t并显示所有提示,ls必须检查关于该类的所有内容,包括它的私有成员和docblock。为此,它需要对所述类进行反思。


    Java反射使得可以在运行时检查类、接口、字段和方法,而不知道编译时类、方法等的名称。大多数情况下,在框架级别,反射的最大好处是可以实现的。如果在运行时需要额外修改来检查、修改、在自身中添加更多字节代码,或者在方法级别、实例变量级别、构造函数级别、注释级别反射等其他程序或框架,则编译的字节代码是有用的。

    假设您有一个方法add(Int a,int b)。等效字节码假设为B1。假设您的系统中有1000个名为add的方法。现在您要在调用方法add之前检查参数ab的值。因此,可以将代码粘附到另一个程序或框架上,该程序或框架使用Object.getClass.getMethod()动态检查字节代码值。有几门课要复习。它可以在调用方法add之前添加更多的操作。但是,程序本身或另一个程序或框架不知道具有名为add的方法的对象。在依赖注入中,主要使用面向方面的反射编程。