为什么创建带有反射的通用setter和getter是一个坏主意?


49

前一段时间,我写了这个问题的答案,它关于如何避免每个可变变量都具有getter和setter方法。当时,我只有一种难以言表的直觉,认为这是一个坏主意,但OP明确要求这样做。我在这里搜索了这可能是个问题的原因,然后发现了这个问题,其答案似乎认为,除非绝对必要,否则使用反射是不好的做法。

那么,有人可以说出为什么应该避免反射的原因,特别是在通用getter和setter的情况下?


12
如果只想减少样板,Lombok和Groovy都将无声但安全地生成吸气剂和吸气剂。
克莱里斯

2
您甚至会如何以Java之类的静态语言来使用这些getter和setter?对我来说,这似乎像是一个初学者。
Esben Skov Pedersen

@EsbenSkovPedersen你会消耗他们很像Mapgetput,这是做事情的一个非常笨拙的方法。
HAEM 2017年

2
IIRC,Lombok在编译时根据适当的注释指示来合成吸气剂/吸气剂,因此它们:不会引起反射的性能损失,可以进行静态分析/内联,可以重构(IntelliJ具有Lombok的插件)
亚历山大

Answers:


117

反思的缺点

反射比直线代码更难理解。

以我的经验,反射是Java中的“专家级”功能。我认为大多数程序员永远不会主动使用反射(即使用反射的库不算在内)。这使得使用这些代码的程序员更难理解。

反射代码无法进行静态分析

假设getFoo我班上有一个吸气剂,我想将其重命名为getBar。如果我不使用反射,则可以在代码库中进行搜索,getFoo并找到使用该getter的每个位置,以便我可以对其进行更新,即使我错过了它,编译器也会抱怨。

但是,如果使用吸气剂的地方是一样的东西callGetter("Foo")callGettergetClass().getMethod("get"+name).invoke(this),那么上面的方法不会找到它,编译器不会抱怨。只有在实际执行代码时,您才会获得NoSuchMethodException。并想象一下,如果该异常(被跟踪)被吞噬了,您会感到痛苦,callGetter因为“它仅与硬编码的字符串一起使用,实际上并不会发生”。(没有人会这样做,有人可能会争论?除了OP 在他的SO回答中确实做到了这一点。如果重命名该字段,则通用设置器的用户将永远不会注意到,除了设置器的极其晦涩的错误却无所事事。如果很幸运,getter的用户可能会注意到控制台输出的被忽略的异常。)

反射代码未由编译器进行类型检查

基本上,这是上述内容的重要内容。反射代码是关于Object。在运行时检查类型。错误是通过单元测试发现的,但前提是您有覆盖范围。(“这只是吸气剂,我不需要对其进行测试。”)基本上,与使用Python相比,使用Java的优势首先使您失去了优势。

反射代码不可用于优化

也许不是从理论上讲,但在实践中,您不会找到为内联或创建内联缓存的JVM Method.invoke。常规方法调用可用于此类优化。这使它们更快。

反射代码通常只是很慢

反射代码所需的动态方法查找和类型检查比正常方法调用慢。如果您将廉价的一站式吸气剂转变为反射型野兽,您可能(我尚未测量过)正在观察几个数量级的减速。

通用getter / setter的缺点

那只是个坏主意,因为您的类现在不再具有封装。它具有的每个字段都是可访问的。您不妨将它们全部公开。


8
您达到了所有要点。几乎是一个完美的答案。我可能会质疑,吸气剂和吸气剂比公共场所要好一些,但更大的观点是正确的。
JimmyJames

2
还值得一提的是,IDE也可以轻松生成getter / setter。
stiemannkj1

26
生产规模项目中的+1调试反射逻辑应被联合国识别为酷刑,并被禁止。
errantlinguist 17-10-9

4
“这些程序员更难理解。” -对于这些程序员很显著很难理解,但它是很难理解每个人,无论多么精通。
BartoszKP

4
“静态分析无法访问反射代码”-这。对于启用重构非常重要。随着静态分析工具变得越来越强大(例如,在最新的Team Foundation Server中进行代码搜索),编写使它们能够使用的代码变得更加有价值。
Dan J

11

因为直通吸气剂/阻气剂是可憎的,没有价值。它们不提供任何封装,因为用户可以通过属性获得实际值。他们是YAGNI,因为您几乎永远不会更改现场存储的实现。

而且因为这会降低性能。至少在C#中,getter / setter将直接内联到单个CIL指令。在您的答案中,您将其替换为3个函数调用,这又需要加载元数据以进行反映。我通常使用10倍作为反映成本的经验法则,但是当我搜索数据时,第一个答案之一就是包含了该答案,并将其接近1000倍。

(而且这使您的代码更难调试,并且因为有更多的滥用方式而损害了可用性,并且由于您现在使用的是魔术字符串而不是适当的标识符,因此也损害了可维护性。)


2
请注意,该问题被标记为Java,它像Beans概念一样令人讨厌。Bean没有公共字段,但是它可以通过getX()setX()可以通过反射找到的方法公开字段。Bean不一定要封装它们的值,它们可能只是DTO。因此,在Java中,吸气剂通常是必要的痛苦。C#属性在各个方面都好得多。
阿蒙(Amon)

11
C#属性是经过修饰的获取器/设置器。但是,是的,Beans概念是一场灾难。
塞巴斯蒂安·雷德尔

6
@amon对,C#想到了一个可怕的想法,并使其更易于实现。
JimmyJames

5
@JimmyJames,这确实不是一个糟糕的主意;即使代码有所更改,您仍可以保持ABI兼容性。我想很多人都没有的问题。
凯西

3
@Casey的范围不限于注释,但是从实现的内部细节构建接口是向后的。这导致了程序设计。有时候,使用getX方法是正确的答案,但是在精心设计的代码中它很少出现。Java中的getter和setter的问题不是围绕它们的样板代码,而是它们是可怕的设计方法的一部分。让事情变得更轻松就像在脸上轻松打孔。
JimmyJames

8

除了已经提出的论点。

它没有提供预期的接口。

用户希望调用foo.getBar()和foo.setBar(value),而不是foo.get(“ bar”)和foo.set(“ bar”,value)

要提供用户期望的界面,您需要为每个属性编写单独的getter / setter。一旦完成,使用反射就没有意义了。


在我看来,这个原因比其他答案更重要。
Esben Skov Pedersen

-4

当然,这不是一个坏主意,这比在普通的Java世界中是个坏主意(当您只想要getter或setter时)。例如,某些框架(spring mvc)或librares已经以这种方式使用它(jstl),一些json o xml解析器正在使用它,如果您想使用DSL,则将使用它。然后,这取决于您的用例场景。

作为事实,没有对与错。

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.