8

编辑2:完全object-oriented实现的程序是否提供高性能?大部分framework都是用它的全部力量编写的。但是,reflection也大量用于实现它,例如 forAOPdependency injection。使用reflection会在一定程度上影响性能。

那么,使用它是一种好习惯reflection吗?是否有一些替代编程语言结构的反射的方法?应该使用到什么程度reflection

4

3 回答 3

10

反思本身和本质上都是缓慢的。有关更多详细信息,请参阅此问题。这是由几个原因造成的。Jon Skeet 很好地解释了它:

检查是否有无参数构造函数检查无参数构造函数的可访问性检查调用者是否有权使用反射计算出(在执行时)需要分配多少空间调用构造函数代码(因为它不会知道之前构造函数是空的)

基本上,反射必须在调用之前执行所有上述步骤,而普通方法调用必须做的少得多。

用于实例化 B 的 JITted 代码非常轻量级。基本上它需要分配足够的内存(除非需要 GC,否则它只是增加一个指针),仅此而已 - 没有真正要调用的构造函数代码;我不知道 JIT 是否跳过它,但无论哪种方式都没有太多工作要做。

话虽如此,在许多情况下,Java 的动态性不足以满足您的需求,而反射提供了一种简单而干净的替代方案。考虑以下场景:

  1. 您有大量代表各种项目的类,即 a CarBoatHouse
  2. 它们都扩展/实现了同一个类:LifeItem.
  3. 您的用户输入 3 个字符串之一,“Car”、“Boat”或“House”。
  4. 您的目标是根据参数访问 LifeItem 的方法。
  5. 想到的第一个方法是构建一个 if/else 结构,并构建想要的LifeItem. 然而,这不是非常可扩展的,一旦你有几十个LifeItem实现就会变得非常混乱。

反射在这里可以提供帮助:它可以用于LifeItem根据名称动态构造对象,因此“汽车”输入将被分派给Car构造函数。突然间,原本可能有数百行 if/else 代码的东西变成了一条简单的反射线。由于引入了带有字符串的 switch 语句,后一种情况在 Java 7+ 平台上不会那么有效,但即便如此,我也希望避免使用具有数百种情况的 switch。在大多数情况下,清洁度之间的区别如下:

没有反射:

public static void main(String[] args) {
   String input = args[0];
   if(input.equals("Car"))
      doSomething(new Car(args[1]));
   else if(input.equals("Boat"))
      doSomething(new Boat(args[1]));
   else if (input.equals("House"))
      doSomething(new House(args[1]));
   ... // Possibly dozens more if/else statements
}

而通过利用反射,它可以变成:

public static void main(String[] args) {    
   String input = args[0];
   try {
      doSomething((LifeItem)Class.forName(input).getConstructor(String.class).newInstance(args[1]));
   } catch (Exception ie) {
      System.err.println("Invalid input: " + input);
   }  
}

就个人而言,我会说后者比第一个更整洁、更简洁、更易于维护。最后,这是个人喜好,但这只是反射有用的众多情况之一。

此外,在使用反射时,您应该尝试缓存尽可能多的信息。换句话说,使用简单、合乎逻辑的东西,比如get(Declared)Method如果你能帮忙就不要到处调用:而是将它存储在一个变量中,这样你就不会在想要使用它时重新获取引用的开销。

所以这些是赞成和反对反思的两个极端。总结一下,如果反射提高了代码的可读性(就像在呈现的场景中那样),那么一定要去做。如果你这样做了,只需考虑减少get*反射调用的数量:这些是最容易修剪的。

于 2013-02-18T16:23:24.990 回答
7

虽然反射比“传统代码”最昂贵,但过早的优化是万恶之源。从长达十年的经验证据来看,我假设通过反射调用的方法几乎不会影响性能,除非它是从重循环中调用的,即使如此,反射也有一些性能增强:

某些反射操作,特别是 Field、Method.invoke()、Constructor.newInstance() 和 Class.newInstance(),已被重写以获得更高的性能。反射调用和实例化比以前的版本快几倍 J2SDK 1.4 中的增强 -

注意上面没有提到方法查找(即Class.getMethod),而选择正确的Method对象通常需要额外的步骤,比如遍历类层次结构同时询问“声明的方法”以防它不是公共的),所以我尽可能将找到的方法保存在合适的映射中,这样下一次的成本就只有 Map.get() 和 Method.invoke() 的成本。我想任何编写良好的框架都可以正确处理这个问题。

还应该考虑如果使用反射,某些优化是不可能的(例如方法内联或转义分析。Java HotSpot™ 虚拟机性能增强)。但这并不意味着必须不惜一切代价避免反射。

但是,我认为使用反射的决定应该基于其他标准,例如代码可读性、可维护性、设计实践等。在自己的代码中使用反射(而不是使用内部使用反射的框架)时,一个有将编译时错误转化为运行时错误的风险,这些错误更难调试。在某些情况下,可以将反射调用替换为传统的 OOP 模式,例如命令或抽象工厂。

于 2013-02-18T06:39:36.473 回答
2

我可以举一个例子(但抱歉,我不能给你看测试结果,因为那是几个月前的事了)。我编写了一个 XML 库(面向自定义项目),它用类 + 注释替换了一些旧的 DOM 解析器代码。我的代码是原始代码的一半。我做了测试,是的,反射更昂贵,但并不多(大约 14-15 秒的执行时间中的 0.3 秒(损失约为 2%))。在不经常执行代码的地方,可以使用反射,但性能损失很小。

此外,我确信我的代码可以改进以获得更好的性能。

所以,我建议这些提示:

  1. 如果你能以一种美丽、紧凑和简洁的方式来使用反射;
  2. 如果您的代码将被执行多次,请不要使用反射;
  3. 如果您需要将大量信息从另一个源(例如 XML 文件)投射到 Java 应用程序,请使用反射;
  4. 反射和注释的最佳用法是代码只执行一次(预加载器)。
于 2013-02-18T10:26:22.523 回答