2010-01-05 54 views
5

以类型安全的方式引用类的方法的任何实用方法?一个基本的例子是,如果我想创造类似下面的效用函数:Java中的类型安全方法反射

public Result validateField(Object data, String fieldName, 
          ValidationOptions options) { ... } 

为了称呼它,我必须做的:

validateField(data, "phoneNumber", options); 

这迫使我要么使用魔法字符串,或者在该字符串的某个地方声明一个常量。

我很确定没有办法用股票Java语言来解决这个问题,但是有没有一种(生产级)预编译器或替代编译器可以提供解决方法? (类似的AspectJ如何扩展Java语言)这将是很好做类似以下代替:

public Result validateField(Object data, Method method, 
          ValidationOptions options) { ... } 

里调用:

validateField(data, Person.phoneNumber.getter, options); 
+0

这是一种常见抱怨反思 - 并且尽可能避免它的一个很好的理由。作为一个必须在我工作的项目中做很多反思的人,我感到你的痛苦。 – aperkins 2010-01-05 23:12:22

+1

我认为'类型安全'这个词有些不对。使用reflection @ java是类型安全的(当涉及到运行时)。类型错误在编译期间不会弹出。 – 2010-01-06 02:18:05

回答

4

正如其他人提到,有没有真正的方法来做到这一点......我还没有看到预编译器支持它。至少可以说,语法会很有趣。即使在您的示例中,它也只能涵盖用户可能想要执行的潜在反射可能性的一小部分,因为它不会处理非标准访问器或采用参数的方法等。

即使它是不可能在编译时检查,如果你想让错误代码尽快失败,那么一种方法是在类初始化时解析引用的Method对象。

假设你有一个实用方法用于查找,也许会抛出错误或运行时异常Method对象:

public static Method lookupMethod(Class c, String name, Class... args) { 
    // do the lookup or throw an unchecked exception of some kind with a really 
    // good error message 
} 

然后在你的班,已经常数preresolve的方法,您将使用:

public class MyClass { 
    private static final Method GET_PHONE_NUM = MyUtils.lookupMethod(PhoneNumber.class, "getPhoneNumber"); 

    .... 

    public void someMethod() { 
     validateField(data, GET_PHONE_NUM, options); 
    } 
} 

至少在第一次加载MyClass时就会失败。

我使用反射很多,尤其是bean属性反射,我刚刚习惯了在运行时延迟异常。但是,这种bean代码风格由于各种其他原因往往会出错,非常动态和全部。对于这两者之间的事情,上述将会有所帮助。

+0

这似乎是一个体面的想法。这比尝试用字段名称定义字符串常量要好,就像我在代码中看到的那样。 – jthg 2010-01-06 15:25:49

+0

是的,这是一种“充分利用它”。不确定谁/为什么我得到了某人的投票。 :)总是很乐意落下投票没有评论。 – PSpeed 2010-01-06 21:58:53

+0

这种方法的另一个好处是当/如果语言获得方法文字,那么它可以想象转换简单的变化。 – PSpeed 2010-01-06 22:00:05

3

没有在语言什么但 - 但我相信,Java 7关闭建议的一部分包括方法文字。

除此之外,我没有任何建议,恐怕。

+0

@Downvoter:小心给个理由? – 2010-01-06 15:16:54

1

以类型安全的方式引用类的方法的任何实际方法?

首先,反射类型安全。只是它是动态输入的,而不是静态类型的。

所以,假设你想要一个静态类型相当于反射,理论上的答案是它是不可能的。考虑这个:

Method m; 
if (arbitraryFunction(obj)) { 
    obj.getClass().getDeclaredMethod("foo", ...); 
} else { 
    obj.getClass().getDeclaredMethod("bar", ...); 
} 

我们可以这样做,以便该运行时类型的异常不会发生?一般来说NO,因为这将需要证明arbitraryFunction(obj)终止。 (这相当于停机问题,这是一般无法解决的问题,并且使用最新的定理证明技术AFAIK难以处理。)

而且我认为这个路障将适用于任何可以将任意Java代码注入用于从对象的类反射选择方法的逻辑中的任何方法。

在我看来,目前唯一适度的实用方法是用生成和编译Java源代码的东西替换反射代码。如果此过程在“运行”应用程序之前发生,则您已满足对静态类型安全性的要求。


我更问反思其中的结果总是相同的。 I.E. Person.class.getMethod("getPhoneNumber", null)将始终返回相同的方法,并且完全可以在编译时解决它。

会发生什么,如果编译包含此代码后级,你改变Person去除getPhoneNumber方法?

你可以肯定,你可以解决getPhoneNumber的唯一途径反思是,如果你能以某种方式防止Person被更改。但是你不能用Java来做到这一点。类的运行时绑定是该语言的基本组成部分。

(有关记录,如果你这样做了,你所谓的非反射性的方法,你会得到当两个类加载某些类型的IncompatibleClassChangeError ...)

+0

感谢您对术语的更正。 我不确定你的答案的其余部分与我的问题有关。如果您在运行时动态地进行反射(即反射的结果可能因输入或其他运行时状态而异),那么是的,您可能无法确保代码不会抛出异常或它会停下来。 – jthg 2010-01-06 15:23:40

+0

我更关心反思,结果总是一样的。 I.E. Person.class.getMethod(“getPhoneNumber”,null)将始终返回相同的方法,并且完全可以在编译时解决它。就像你如何做Person.class来获得一个Class对象一样,能够做一些像Person.getPhoneNumber.method这样的方法来获得一个Method对象将会很有帮助。 – jthg 2010-01-06 15:24:12

0

被嘲讽框架的启发,我们能想出的语法如下:

validator.validateField(data, options).getPhoneNumber(); 
Result validationResult = validator.getResult(); 

诀窍是通用的声明:

class Validator { 
    public <T> T validateField(T data, options) {...} 
} 

现在该方法的返回类型是一样的你数据对象的类型,您可以使用代码完成(和静态检查)来访问所有方法,包括getter方法。

作为缺点,代码不太直观,因为对getter的调用实际上并没有得到任何东西,而是指示验证器验证该字段。

另一个可能的选择是将注释字段的数据类:

class FooData { 
    @Validate(new ValidationOptions(...)) 
    private PhoneNumber phoneNumber; 
} 

然后只要致电:

FooData data; 
validator.validate(data); 

根据注释选项验证所有领域。

2

Java错过糖的语法来做一些像Person.phoneNumber.getter那么好的事情。但是,如果Person是一个接口,则可以使用动态代理来记录getter方法。您可以使用CGLib在非final类上记录方法,就像Mockito做的那样。

MethodSelector<Person> selector = new MethodSelector<Person>(Person.class); 
selector.select().getPhoneNumber(); 
validateField(data, selector.getMethod(), options); 

代码MethodSelector:https://gist.github.com/stijnvanbael/5965609

0

框架picklock让您做到以下几点:

class Data { 
    private PhoneNumber phoneNumber; 
} 

interface OpenData { 
    PhoneNumber getPhoneNumber(); //is mapped to the field phoneNumber 
} 

Object data = new Data(); 
PhoneNumber number = ObjectAccess 
    .unlock(data) 
    .features(OpenData.class) 
    .getPhoneNumber(); 

此作品以类似的方式制定者和私有方法。当然,这只是反思的一个包装,但在解锁时不会发生异常,而不是在通话时发生。如果你在构建时需要它,你可以写一个单元测试:

assertThat(Data.class, providesFeaturesOf(OpenData.class)); 
2

退房http://jodd.org/doc/methref.html。它使用Jodd代理库(Proxetta)来代理你的类型。不确定其性能特征,但确实提供了类型安全性。

一个例子:假设Str.class有方法.boo(),并希望得到它的名字作为字符串"boo"

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

// `.to()` returns a proxied instance of `Str` upon which you 
// can call `.boo()` Methods on this proxy are empty except when 
// you call them, the proxy stores the method's name. So doing this 
// gets the proxy to store the name `"boo"`. 

m.to().boo(); 

// You can bget the name of the method you called by using `.ref()`: 

m.ref(); // returns "boo"         

还有更多比上面的例子中的API:http://jodd.org/api/jodd/methref/Methref.html