代码之家  ›  专栏  ›  技术社区  ›  Lehane

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

  •  1823
  • Lehane  · 技术社区  · 16 年前

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

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

    21 回复  |  直到 6 年前
        1
  •  1511
  •   Jishnu Prathap    7 年前

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

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

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

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

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

    有一些很好的反思例子可以让你开始学习 http://docs.oracle.com/javase/tutorial/reflect/index.html

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

    从注释更新:

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

        2
  •  210
  •   Sheharyar    9 年前

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

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

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

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

        3
  •  94
  •   bwegs    6 年前

    我最喜欢的反射方法之一是下面的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();
    }
    
        4
  •  66
  •   Gary Sheppard    6 年前

    反射的使用

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

    可扩展性特征

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

    反射的缺点

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

    • 性能开销

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

    • 安全限制

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

    • 内部暴露

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

    来源: The Reflection API

        5
  •  37
  •   toolkit    16 年前

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

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

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

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

        6
  •  32
  •   Mendelt    16 年前

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

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

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

        7
  •  31
  •   Nikhil Shekhar    11 年前

    反射允许在运行时动态地实例化新对象、调用方法和对类变量执行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");
    

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

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

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

        8
  •  20
  •   human.js    12 年前

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

        9
  •  19
  •   VedantK    9 年前

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

    一个快速的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

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

    • Spring使用bean配置,例如:


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

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

    然后,它将再次使用反射为<property>元素获取适当的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);
    
        10
  •  14
  •   DeadChex    12 年前

    根据我的理解:

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

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

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

        11
  •  14
  •   roottraveller    8 年前

    反射 是用于检查或修改 方法、类、接口 在运行时。

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

    这个 java.lang java.lang.reflect 包为Java反射提供类。

    反射 可用于获取有关

    1. 等级 这个 getClass() 方法用于获取对象所属类的名称。

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

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

    这个 反射API 主要用于:

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

    使用反射的优点:

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

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

    缺点:

    性能开销: 反射操作的性能比不反射操作的性能要慢,应该避免在性能敏感的应用程序中经常调用的代码部分使用反射操作。

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

    裁判: Java Reflection javarevisited.blogspot.in

        12
  •  13
  •   Jorge Córdoba    16 年前

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

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

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

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

        13
  •  13
  •   Honinbo Shusaku    9 年前

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

    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);
        }
    
    }
    
        14
  •  10
  •   Yoon5oo JBE    6 年前

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

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

        15
  •  8
  •   catch32    10 年前

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

    反射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;
       }    
    }
    
        16
  •  8
  •   Community CDub    8 年前

    从Java文档 page

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

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

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

    它包括以下功能。

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

    看看这个 documentation 所暴露方法的链接 Class 班级。

    由此 article (作者:Dennis Sosnoski,Sosnoski Software Solutions,Inc.总裁)以及 article (安全探索pdf):

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

    反射用户:

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

    反射的缺点:

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

    一般虐待:

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

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

    How do I read a private field in Java?

    总结:

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

        17
  •  8
  •   Mohammed Sarfaraz    8 年前

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

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

        18
  •  5
  •   Rohil_PHPBeginner    10 年前

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

    IE:基于任何数据的动态类、函数、构造函数 (xml/array/sql结果/硬编码等)

        19
  •  5
  •   Yoon5oo JBE    6 年前

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

        20
  •  2
  •   BSeitkazin    6 年前

    我想举例回答这个问题。首先 Hibernate 项目使用 Reflection API 生成 CRUD 在正在运行的应用程序和持久性存储之间架起桥梁的语句。当领域发生变化时, 冬眠 必须了解它们才能将它们持久化到数据存储,反之亦然。

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

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

    我们从Java 7 MethodHandles ,其作用是 反射API . 在项目中,要使用记录器,只需复制粘贴下一个代码:

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

    因为在这种情况下很难犯打字错误。

        21
  •  -7
  •   abishkar bhattarai    11 年前

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

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