(过度)使用反射是个坏习惯吗?


16

如果大大减少了样板代码的数量,使用反射是否是一种好习惯?

基本上,在性能和一方面的可读性与另一方面的抽象/自动化/简化样板代码之间要进行权衡。

编辑:这是推荐使用反射的示例

举一个例子,假设有一个抽象类Base,其具有10个字段和具有3个亚类SubclassASubclassB并且SubclassC每个具有10个不同的字段; 它们都是简单的豆子。问题是您有两个Base类型引用,并且想要查看它们的对应对象是否属于相同(子)类型并且相等。

作为解决方案,有一个原始解决方案,在该解决方案中,您首先要检查类型是否相等,然后检查所有字段,或者可以使用反射并动态查看它们是否属于同一类型,并遍历以“ get”开头的所有方法(约定超出配置),请同时在两个对象上调用它们,并在结果上调用equals。

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}

20
过度使用任何东西都是坏习惯。
TulainsCórdova13年

1
@ user61852是的,太多的自由会导致专政。一些古希腊人已经知道这一点。
ott--

6
“过多的水对您不利。显然,太多恰恰是数量这是过度的,这是什么意思!” -史蒂芬·弗莱
乔恩·珀迪

4
“任何时候你发现自己写形式的代码‘如果对象的类型是T1的,然后做一些事情,但如果它的类型T2的,然后去做别的事情,’拍自己。javapractices.com/topic/TopicAction.do?Id = 31
rm5248

Answers:


25

反射是为特定目的而创建的,以发现在编译时未知的类的功能,类似于C中的dlopendlsym功能。在此之外的任何使用都应严格检查。

您是否曾经想到Java设计人员自己遇到过此问题?这就是为什么几乎每个类都有一个equals方法的原因。不同的类对平等的定义不同。在某些情况下,派生对象可能等于基础对象。在某些情况下,可以根据没有吸气剂的私有领域来确定平等。你不知道

这就是每个想要自定义相等性的对象都应实现一个equals方法的原因。最终,您需要将对象放入集合中,或将它们用作哈希索引,然后equals无论如何都必须实现。其他语言则采用不同的方式,但是Java使用equals。您应该遵守语言惯例。

另外,如果将“样板”代码放入正确的类中,则很难搞清楚。反射增加了额外的复杂性,这意味着增加了错误的机会。例如,在您的方法中,如果一个对象返回null某个字段而另一个对象不返回,则两个对象被视为相等。如果您的一个获取器没有适当的返回一个对象equals怎么办?你if (!object1.equals(object2))会失败的。同样容易出错的是,很少使用反射,因此程序员对它的陷阱不太熟悉。


12

过度使用反射可能取决于所使用的语言。在这里,您正在使用Java。在这种情况下,应谨慎使用反射,因为通常它只是不良设计的一种解决方法。

因此,您正在比较不同的类,这是方法覆盖的完美问题。请注意,两个不同类的实例永远不应被视为相等。仅当您具有相同类的实例时,才可以比较是否相等。有关如何正确实现相等比较的示例,请参见/programming/27581/overriding-equals-and-hashcode-in-java


16
+1-我们中有些人认为对反射的任何使用都是危险信号,表示设计不良。
Ross Patterson

1
在这种情况下,最有可能希望有一个基于equals的解决方案,但是作为一个一般性的想法,反射解决方案出了什么问题?实际上,它非常通用,不需要在每个类和子类中显式编写equals方法(尽管可以通过良好的IDE轻松生成它们)。
m3th0dman

2
@RossPatterson为什么?
m3th0dman

2
@ m3th0dman因为它会导致类似您的compare()方法的事情,假设任何以“ get”开头的方法都是吸气剂,因此作为比较操作的一部分,可以安全且适当地进行调用。这违反了对象的预期接口定义,尽管这很方便,但几乎总是错误的。
罗斯·帕特森

4
@ m3th0dman它违反了封装-基类必须访问其子类中的属性。如果它们是私有的(或getter私有的)怎么办?那么多态呢?如果我决定添加另一个子类,并且要在其中进行不同的比较?好吧,基类已经为我做了,我无法更改。如果吸气剂懒惰地装载东西怎么办?我想要比较方法吗?我什至怎么知道一个get以getter 开头的方法,而不是返回某些东西的自定义方法?
苏珊(Sulthan)2013年

1

我认为您在这里有两个问题。

  1. 我应该有多少个动态代码与静态代码?
  2. 如何表达自定义版本的相等性?

动态与静态代码

这是一个长期存在的问题,答案很有根据。

一方面,您的编译器非常擅长捕获各种错误代码。它通过各种形式的分析来做到这一点,类型分析是一种常见的形式。它知道您不能Banana在期望使用的代码中使用对象Cog。它通过编译错误告诉您。

现在,仅当它可以从上下文中推断出接受的类型和给定的类型时,才可以执行此操作。可以推断出多少,以及推断的普遍程度,在很大程度上取决于所使用的语言。Java可以通过诸如继承,接口和泛型之类的机制来推断类型信息。里程确实会有所不同,其他一些语言提供的机制较少,而另一些提供的机制则更多。它仍然归结为编译器可以知道是真的。

另一方面,您的编译器无法预测外来代码的形状,有时,通用算法可以在许多类型上表达,而使用语言的类型系统则不容易表达这种算法。在这些情况下,编译器不能总是事先知道结果,甚至可能不知道要问什么问题。反射,接口和Object类是Java处理这些问题的方法。您将必须提供正确的检查和处理,但是拥有这种代码并非不健康。

无论是使代码非常特定还是非常通用,都取决于您要处理的问题。如果可以使用类型系统轻松地表达它,请这样做。让编译器发挥自己的优势并为您提供帮助。如果类型系统可能无法事先知道(外部代码),或者该类型系统不适合您的算法的一般实现,则反射(和其他动态方式)是使用的正确工具。

请注意,跳出语言类型系统令人惊讶。想象一下,走到你的朋友那里,用英语开始对话。突然从西班牙语,法语和广东话中删除了几句话,准确表达了您的想法。上下文会告诉您的朋友很多,但他们也可能不太了解如何处理那些会引起各种误解的单词。处理这些误解是否比用更多的单词用英语解释这些想法更好

自定义平等

虽然我知道Java在很大程度上依赖于equals用于比较两个对象的方法,但它并不总是适合于给定的上下文。

还有另一种方法,它也是Java标准。它称为比较器

至于如何实现比较器,将取决于您所比较的内容以及方式。

  • 可以将其应用于任何两个对象,而不管其具体equals实现如何。
  • 它可以实现用于处理任何两个对象的常规(基于反射)比较方法。
  • 可以为通常比较的对象类型添加样板比较功能,以保证类型的安全性和优化性。

1

我宁愿尽可能避免反射式编程,因为它

  • 使代码更难由编译器进行静态检查
  • 使代码难以推理
  • 使代码难以重构

它的性能也比简单的方法调用差很多。它曾经慢了一个数量级或更多。

静态检查代码

任何反射性代码都使用字符串查找类和方法。在原始示例中,它正在寻找以“ get”开头的任何方法;它将返回getter,但还会返回其他方法,例如“ gettysburgAddress()”。可以在代码中加强这些规则,但是重点仍然是运行时检查。IDE和编译器无济于事。通常,我不喜欢“字符串型”或“原始型”代码。

难以推理

反射性代码比简单的方法调用更冗长。更多的代码=更多的错误,或者至少更多的潜在错误,更多的代码可读取,测试等。更少的是更多。

难以重构

因为代码是基于字符串/动态的;一旦出现反射,您就无法使用IDE的重构工具以100%的置信度来重构代码,因为IDE无法获取反射的用途。

基本上,尽可能避免在通用代码中进行反思;寻找改进的设计。


0

根据定义,过度使用任何东西都是不好的,对吧?因此,让我们暂时摆脱(结束)。

您会把春季对反射的大量内部使用称为坏习惯吗?

Spring通过使用注释来驯服反射-Hibernate也是如此(可能还有数十/数百种其他工具)。

如果您在自己的代码中使用它,请遵循这些模式。使用批注以确保用户的IDE仍然可以为他们提供帮助(即使您是代码的唯一“用户”,粗心的使用反射也可能最终会使您陷入困境。)

但是,如果不考虑开发人员将如何使用您的代码,即使最简单的反射使用也可能过度使用。


0

我认为这些答案大多数都错了。

  1. 是的,您应该编写equals()hashcode(),如@KarlBielefeldt所述。

  2. 但是,对于具有许多领域的课程而言,这可能是乏味的样板。

  3. 因此,这取决于

    • 如果只需要等号和哈希码 很少,这是务实的,大概OK,使用通用的反射计算。至少作为快速而肮脏的第一遍。
    • 但是如果您需要很多东西,例如这些对象放入HashTables中,那么性能将成为问题,则一定要写出代码。它将更快。
  4. 另一种可能性:如果您的课程确实有太多字段,以至于编写等价的文字很乏味,请考虑

    • 将字段放入地图。
    • 您仍然可以编写自定义的getter和setter
    • Map.equals()你的比较。(或Map.hashcode()

例如(注意:忽略空检查,应该使用枚举代替字符串键,很多未显示...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.