是否有任何实用的方法以类型安全的方式引用类的方法?一个基本的例子是,如果我想创建类似下面的效用函数:
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 :(得分: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
}
然后在你的类中,使用常量来预先解析你将使用的方法:
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代码往往会出错,而且非常动态。对于介于两者之间的事情,以上内容会有所帮助。
答案 1 :(得分:3)
语言中还没有 - 但我相信Java 7的闭包提案的一部分包括方法文字。
除此之外我没有任何建议,我很害怕。
答案 2 :(得分:3)
结帐https://jodd.org/ref/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 get the name of the method you called by using `.ref()`:
m.ref(); // returns "boo"
API比上面的示例更多:https://oblac.github.io/jodd-site/javadoc/jodd/methref/Methref.html
答案 3 :(得分:2)
Java错过了语法糖来做一些和Person.phoneNumber.getter
一样好的事情。但是如果Person是一个接口,你可以使用动态代理记录getter方法。您可以使用CGLib记录非最终类的方法,就像Mockito所做的那样。
MethodSelector<Person> selector = new MethodSelector<Person>(Person.class);
selector.select().getPhoneNumber();
validateField(data, selector.getMethod(), options);
MethodSelector的代码:https://gist.github.com/stijnvanbael/5965609
答案 4 :(得分: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
...)
答案 5 :(得分: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);
根据带注释的选项验证所有字段。
答案 6 :(得分: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();
这与setter和private方法类似。当然,这只是反射的包装器,但是在解锁时不会在通话时发生异常。如果你在构建时需要它,你可以编写一个单元测试:
assertThat(Data.class, providesFeaturesOf(OpenData.class));
答案 7 :(得分:0)
使用Manifold的@Jailbreak
进行编译时类型安全访问私有字段,方法等。
@Jailbreak Foo foo = new Foo();
foo.privateMethod();
foo.privateMethod("hey");
foo._privateField = 88;
public class Foo {
private final int _privateField;
public Foo(int value) {
_privateField = value;
}
private String privateMethod() {
return "hi";
}
private String privateMethod(String param) {
return param;
}
}
了解更多:type-safe-reflection
答案 8 :(得分:0)
我找到了一种使用 Lambda 获取 Method
实例的方法。目前它只适用于接口方法。
它使用 net.jodah:typetools
工作,这是一个非常轻量级的库。
https://github.com/jhalterman/typetools
public final class MethodResolver {
private interface Invocable<I> {
void invokeWithParams(I instance, Class<?>[] parameterTypes) throws Throwable;
}
interface ZeroParameters<I, R> extends Invocable<I> {
R invoke(I instance) throws Throwable;
@Override
default void invokeWithParams(I instance, Class<?>[] parameterTypes) throws Throwable {
invoke(instance);
}
}
public static <I, R> Method toMethod0(ZeroParameters<I, R> call) {
return toMethod(ZeroParameters.class, call, 1);
}
interface OneParameters<I, P1, R> extends Invocable<I> {
R invoke(I instance, P1 p1) throws Throwable;
@Override
default void invokeWithParams(I instance, Class<?>[] parameterTypes) throws Throwable {
invoke(instance, param(parameterTypes[1]));
}
}
public static <I, P1, R> Method toMethod1(OneParameters<I, P1, R> call) {
return toMethod(OneParameters.class, call, 2);
}
interface TwoParameters<I, P1, P2, R> extends Invocable<I> {
R invoke(I instance, P1 p1, P2 p2) throws Throwable;
@Override
default void invokeWithParams(I instance, Class<?>[] parameterTypes) throws Throwable {
invoke(instance, param(parameterTypes[1]), param(parameterTypes[2]));
}
}
public static <I, P1, P2, R> Method toMethod2(TwoParameters<I, P1, P2, R> call) {
return toMethod(TwoParameters.class, call, 3);
}
private static final Map<Class<?>, Object> parameterMap = new HashMap<>();
static {
parameterMap.put(Boolean.class, false);
parameterMap.put(Byte.class, (byte) 0);
parameterMap.put(Short.class, (short) 0);
parameterMap.put(Integer.class, 0);
parameterMap.put(Long.class, (long) 0);
parameterMap.put(Float.class, (float) 0);
parameterMap.put(Double.class, (double) 0);
}
@SuppressWarnings("unchecked")
private static <T> T param(Class<?> type) {
return (T) parameterMap.get(type);
}
private static <I> Method toMethod(Class<?> callType, Invocable<I> call, int responseTypeIndex) {
Class<?>[] typeData = TypeResolver.resolveRawArguments(callType, call.getClass());
Class<?> instanceClass = typeData[0];
Class<?> responseType = responseTypeIndex != -1 ? typeData[responseTypeIndex] : Void.class;
AtomicReference<Method> ref = new AtomicReference<>();
I instance = createProxy(instanceClass, responseType, ref);
try {
call.invokeWithParams(instance, typeData);
} catch (final Throwable e) {
throw new IllegalStateException("Failed to call no-op proxy", e);
}
return ref.get();
}
@SuppressWarnings("unchecked")
private static <I> I createProxy(Class<?> instanceClass, Class<?> responseType,
AtomicReference<Method> ref) {
return (I) Proxy.newProxyInstance(MethodResolver.class.getClassLoader(),
new Class[] {instanceClass},
(proxy, method, args) -> {
ref.set(method);
return parameterMap.get(responseType);
});
}
}
用法:
Method method = MethodResolver.toMethod2(SomeIFace::foobar);
System.out.println(method); // public abstract example.Result example.SomeIFace.foobar(java.lang.String,boolean)
Method get = MethodResolver.<Supplier, Object>toMethod0(Supplier::get);
System.out.println(get); // public abstract java.lang.Object java.util.function.Supplier.get()
Method accept = MethodResolver.<IntFunction, Integer, Object>toMethod1(IntFunction::apply);
System.out.println(accept); // public abstract java.lang.Object java.util.function.IntFunction.apply(int)
Method apply = MethodResolver.<BiFunction, Object, Object, Object>toMethod2(BiFunction::apply);
System.out.println(apply); // public abstract java.lang.Object java.util.function.BiFunction.apply(java.lang.Object,java.lang.Object)
不幸的是,您必须根据参数计数以及该方法是否返回 void 来创建新的接口和方法。
但是,如果您的方法签名/参数类型有些固定/有限,那么这将变得非常方便。