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

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


当前回答

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

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

}

其他回答

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

例如,假设您在Java中有一个未知类型的对象,如果存在,您希望对其调用“doSomething”方法。Java的静态类型系统并不是为了支持这一点而设计的,除非对象符合已知的接口,但是使用反射,您的代码可以查看对象并找出它是否有一个名为“doSomething”的方法,如果需要,可以调用它。

因此,给您一个Java代码示例(假设所讨论的对象是foo):

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++

我只是想对所有列出的内容补充几点。

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

反射的使用

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

可扩展性功能

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

反射的缺点

反射是强大的,但不应滥用。如果可以在不使用反射的情况下执行一个操作,那么最好避免使用它。

性能开销

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

安全性限制

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

内部构件暴露

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

来源:反射API

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

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

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

Junit特别使用Reflection来测试Private/Protected方法。

对于私有方法,

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

根据我的理解:

反射允许程序员动态访问程序中的实体。也就是说,当程序员对应用程序进行编码时,如果不知道类或其方法,他可以通过使用反射来动态地(在运行时)使用此类类。

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

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