如何在Java中获取Method对象而不使用方法字符串名称

21

我正在寻找一种方便的方法,来获取一个方法的Method对象。想法是:

Method fooMethod = getMethod( new MyObject().foo() ) // returns method "foo" in MyObject

显而易见的方法是将方法名作为字符串使用:

Method fooMethod = MyObject.class.getMethod("foo")

但是我希望避免这种情况,因为如果我重命名foo(),那么代码将无法工作,或者我必须在使用该字符串的所有地方进行更改。

我的使用案例是我想使用类似于PropertyChangeListeners的东西,但是它们依赖于方法名称作为字符串。我想安全地使用实际方法而不依赖于字符串。

有什么方法可以以一种“重命名安全”的方式获取该方法吗?

更新:我想找到一个不依赖于IDE特性的纯Java解决方案。


1
虽然其他人提到IDE可能会减轻一些痛苦,但你所描述的目前不可能实现。我似乎记得有一个功能请求/JSR提出了类似"MyObject#foo"这样的语法,但是找不到它了。 - Arnout Engelen
尽管这似乎不可能,但我正在寻找目前可用的可能解决方法。 - Andrejs
2
@ArnoutEngelen Java 8的lambda表达式预计具有类似的功能:http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html(第8节)。请注意,这不会给您一个方法,而是一个lambda表达式,您可以随后调用它。 - yshavit
一些集成开发环境建议更改字符串,这些字符串可能是指类、方法等,当您重命名某些内容时。它们可以建议在文本文件中进行更改,例如属性和注释。即使您只想更改所谓的东西,以便所有代码保持同步,这也可能很有用。 - Peter Lawrey
1
可能是如何获取Java 8方法引用的MethodInfo?的重复问题。 - Dean Xu
8个回答

13

Java 8方法引用非常适合这个问题,但难点在于获取底层方法,因为方法引用语法本身会生成一个不透明的lambda对象。

在搜索了一段时间后找到了下面的内容:

http://benjiweber.co.uk/blog/2013/12/28/typesafe-database-interaction-with-java-8/

聪明的技巧——在代理对象上调用方法并记录方法名称。我没有尝试过,但看起来很有前途。


5
好的,我会尽力进行翻译。"请尝试将更多信息添加到回答本身中,因为链接可能会失效 :)。" - achedeuzot
根据您实际想要做的事情,java.lang.reflect.Proxy也可能能够胜任。而且不需要外部依赖。 - Benjamin Maurer
据我所知,代理只能窥探接口类,而不能窥探实现类。 - init_js

10
在你的方法调用中: Method me = (new MethodNameHelper(){}).getMethod();

意思是通过创建一个匿名内部类,来获取方法的引用。
/**
 * Proper use of this class is
 *     Method me = (new MethodNameHelper(){}).getMethod();
 * the anonymous class allows easy access to the method name of the enclosing scope.
 */
public class MethodNameHelper {
  public Method getMethod() {
    return this.getClass().getEnclosingMethod();
  }
}

你是在建议对原始的MyObject进行子类化,并覆盖foo()方法以使用这个帮助程序吗? - Andrejs
不需要对MyObject进行子类化。让foo()调用**Method me = (new MethodNameHelper(){}).getMethod();**,并将结果保存或按照您的意愿使用即可。您没有提及您想如何使用这个Method对象。 - Jim
1
好的。问题在于,函数foo()可能已经有了一些代码,我不太想去改动它或者这些代码是来自第三方的。不过,即便如此,我应该如何把方法me返回给调用代码(例如main函数)呢? - Andrejs
那么我误解了你的用例。如果你不控制foo()的代码,那么你的代码与它的连接是通过名称,并且Reflection是你最好的选择。我原以为你想编写一个foo(),以便它或其他人可以利用它的方法。在这种情况下,对foo()的第一次调用计算其方法并以其他人需要的方式存储它(例如在Map中)。 - Jim
4
由于您不一定想要调用该方法以获取其引用,因此该技术的价值有限。理想情况下,它们应该是不相关的操作。不幸的是,Java设计中存在缺陷,许多编译器已知但只能在运行时(通过反射)暴露的有用信息未被揭示。 - Magnus
2
MethodNameHelper 应该是抽象的。 - shmosel

9

实际上有一个可以做到这点的库:

Jodd MethRef - 强类型方法名称引用

https://jodd.org/ref/methref.html

Methref<Str> m = Methref.on(Str.class);  // create Methref tool

// example #1
m.to().boo();
m.ref();                               // returns String: 'boo'

1
非常酷,但如果它们返回“方法”而不仅仅是名称,那就更酷了。 - shmosel
现在是Jodd Proxetta:https://proxetta.jodd.org/refs/methref - undefined

7
我们已经发布了一个小型库reflection-util,可以用来捕获一个方法。
举个例子:
class MyClass {

    public void myMethod() {
    }

}

Method method = ClassUtils.getVoidMethod(MyClass.class, MyClass::myMethod);
System.out.println(method.getName()); // prints "myMethod"

实现细节:使用 ByteBuddy 创建一个 MyClass 的代理子类,并捕获方法调用以获取其名称。ClassUtils 缓存这些信息,因此我们不需要在每次调用时创建新的代理。

4

您可能想使用注释。您可以为要检索的每个方法创建自定义注释,并使用反射从对象中提取该方法。

您可以安全地重命名方法,您的代码将仍然有效。

如果您想要检索多个方法,则使用只有一个字符串值的注释,该值将根据每个方法而变化,然后您的反射代码将查找具有该字符串值的那些注释。只要保留注释的字符串值不变,您仍然可以重命名您的方法。


1

最安全的做法是使用具有重构功能的IDE,它足够智能,可以识别并替换该字符串方法名称。JetBrains的IntelliJ可以做到这一点-我刚刚证明了它。


默认情况下,Eclipse不会对方法执行相同的操作 - 只有类。我可以使用搜索,但我并不总是知道需要查找字符串引用。 - Andrejs
1
就像我说的那样,IntelliJ知道如何做到这一点。我建议你使用更好的IDE。Eclipse不是最好的选择。 - duffymo
IntellJ很不幸只能使用启发式方法。例如,同名但不同类的方法可能会被重命名为完全不相关的字符串。当您尝试重命名getIdisActive等方法时,情况尤其严重,因为这些方法可能存在于许多完全不相关的类中。 - Svante
我在使用IntelliJ时没有任何问题。 - duffymo

-1

使用反射,你会失去编译时的安全性,这是其本质所决定的。

相反,考虑一下是否真的需要使用反射。例如,你可以使用Runnable来表示你的方法:

Runnable foo = new Runnable() {
    @Override
    public void run() {
        new MyObject().foo();
    }
}

...

foo.run();

或者如果您需要表示具有返回类型的方法,请查看Callable - 或者更好的是,Guava的Function


可以,但这也意味着需要包装每个需要使用的方法。这并不是很方便。 - Andrejs
我想看一下你是如何做出这个设计决策的解释。我还不确定反射在你所做的事情中是否必要。基于运行时查找的这种设计将来会很难维护和调试。 - Paul Bellora
将PropertyChangeListener用例添加到原始问题中。 - Andrejs

-1

您可以使用IDE重构,它会处理所有字符串出现的情况。

您不能通过反射以方法之外的方式引用方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接