提问人:SwordOfSouls 提问时间:10/28/2023 更新时间:10/31/2023 访问量:63
MethodHandle 查找问题
MethodHandle Lookup Issues
问:
我最近需要制作一个相对简单的事件系统。(id -> 使用者)这将能够订阅基于注释的注释。然后我想起 Java 不是 C#,并试图寻找一种方法来实现这一点。在许多类似的线程之后,我遇到了这个 GitHub Gist,并尝试实现它。我成功地做到了这一点,但是没有足够早地意识到该方法是特定于位置的,并且仅适用于调用它的类。Method
MethodHandles.lookup()
消费者工厂
public class ConsumerFactory {
private final Method consumerMethod;
private final MethodType consumerMethodType;
private final Map<Method, Consumer<?>> methodCache = new HashMap<>();
public ConsumerFactory() {
consumerMethod = findLambdaMethod(Consumer.class);
consumerMethodType = MethodType.methodType(consumerMethod.getReturnType(), consumerMethod.getParameterTypes());
}
public <T, L> Consumer<T> createConsumer(L instance, Method implMethod) throws Throwable {
Consumer<T> cached = (Consumer<T>) methodCache.get(implMethod);
if(cached==null) {
Class<?> implType = implMethod.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup().in(implType);
MethodType implMethodType = MethodType.methodType(implMethod.getReturnType(), implMethod.getParameterTypes());
MethodHandle implMethodHandle = lookup.findVirtual(implType, implMethod.getName(), implMethodType);
MethodType invokedMethodType = MethodType.methodType(Consumer.class, implType);
CallSite metaFactory = LambdaMetafactory.metafactory(
lookup,
consumerMethod.getName(), invokedMethodType, consumerMethodType,
implMethodHandle, implMethodType);
MethodHandle factory = metaFactory.getTarget();
Consumer<T> consumer = (Consumer<T>) factory.invoke(instance);
methodCache.put(implMethod, consumer);
return consumer;
}
return cached;
}
private Method findLambdaMethod(Class<?> type) {
if (!type.isInterface()) {
throw new IllegalArgumentException("This must be interface: " + type);
}
Method[] methods = getAllMethods(type);
if (methods.length == 0) {
throw new IllegalArgumentException("No methods in: " + type.getName());
}
Method targetMethod = null;
for (Method method : methods) {
if (isInterfaceMethod(method)) {
if (targetMethod != null) {
throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
}
targetMethod = method;
}
}
if (targetMethod == null) {
throw new IllegalArgumentException("No method in: " + type.getName());
}
return targetMethod;
}
private Method[] getAllMethods(Class<?> type) {
LinkedList<Method> result = new LinkedList<>();
Class<?> current = type;
do {
result.addAll(Arrays.asList(current.getMethods()));
} while ((current = current.getSuperclass()) != null);
return result.toArray(new Method[0]);
}
private boolean isInterfaceMethod(Method method) {
return !method.isDefault() && Modifier.isAbstract(method.getModifiers());
}
}
我能做些什么来保持这种想法功能吗?理想情况下,最终是这样的 .我正在考虑强行创建一个类,但我不确定这是否是最好的主意。Map<String, Consumer<Event>>
Lookup
谢谢大家的帮助!
答:
如果你的问题是你得到一个异常,如,问题出在以下行:LambdaConversionException: Invalid caller: …
MethodHandles.Lookup lookup = MethodHandles.lookup().in(implType);
你只需要把它改成
MethodHandles.Lookup lookup = MethodHandles.lookup();
这将在 的上下文中执行操作,并要求允许访问目标方法。但是,如果您当前的代码成功地在查找对象上执行了操作,并继续以上述异常结束,则意味着已提供可访问性。ConsumerFactory
ConsumerFactory
findVirtual
但请注意,代码过于复杂。尽管调用了 a ,但迭代超类层次结构已经返回所有方法,包括从超级接口继承的方法,但无论如何都会返回接口。getAllMethods
getMethods()
Class
public
getSuperclass()
null
此外,正如何时在 Java 中使用 LinkedList 而不是 ArrayList 中所解释的,很少需要 a,这个用例也不例外。LinkedList
然后,排除 ,所以这个测试是多余的。另一方面,必须跳过与方法匹配的方法。如果不这样做,它就会失败,例如。问题是您是要修复以涵盖所有情况还是完全放弃它,因为您只对我们知道该方法是 .isAbstract
isDefault
public
java.lang.Object
Comparator
findLambdaMethod
Consumer
void accept(Object)
此外,从 a 到 a 的转换不需要手动完成。考虑到所有简化,我们最终得到Method
MethodHandle
public class ConsumerFactory {
private final Map<Method, Consumer<?>> methodCache = new HashMap<>();
public ConsumerFactory() {}
public <T, L> Consumer<T> createConsumer(L instance, Method implMethod) throws Throwable {
Consumer<T> cached = (Consumer<T>) methodCache.get(implMethod);
if(cached==null) {
// avoid cryptic errors by checking this explicitly
if(implMethod.getParameterCount() != 1 || Modifier.isStatic(implMethod.getModifiers()))
throw new IllegalArgumentException("not suitable for Consumer: " + implMethod);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle implMethodHandle = lookup.unreflect(implMethod);
MethodType implMethodType = MethodType.methodType(void.class, implMethodHandle.type().parameterType(1));
MethodType invokedMethodType = MethodType.methodType(Consumer.class, implMethod.getDeclaringClass());
CallSite metaFactory = LambdaMetafactory.metafactory(
lookup, "accept", invokedMethodType,
MethodType.methodType(void.class, Object.class),
implMethodHandle, implMethodType);
Consumer<T> consumer = (Consumer<T>) metaFactory.getTarget().invoke(instance);
methodCache.put(implMethod, consumer);
return consumer;
}
return cached;
}
}
但请记住,您也可以在 上创建调用,甚至可以通过 lambda 表达式。所以上面的代码并不是严格需要的。如果方法失败,调用 a 也可以作为回退。Consumer
invoke
Method
invoke
Method
LambdaMetafactory
如果要保留用于查找要为函数接口实现的方法的方法,下面是修订后的版本:
private Method findLambdaMethod(Class<?> type) {
if(!type.isInterface()) {
throw new IllegalArgumentException("This must be interface: " + type);
}
Method targetMethod = null;
for(Method method: type.getMethods()) {
if(isCandidate(method)) {
if(targetMethod != null) {
throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
}
targetMethod = method;
}
}
if(targetMethod == null) {
throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
}
return targetMethod;
}
private boolean isCandidate(Method method) {
if(!Modifier.isAbstract(method.getModifiers()))
return false;
// check for redeclarations of public java.lang.Object methods
if(method.getParameterCount() == 0) {
String name = method.getName();
return !name.equals("hashCode") && !name.equals("toString");
}
if(method.getParameterCount() == 1)
return !method.getName().equals("equals") || method.getParameterTypes()[0] != Object.class;
return true;
}
下一个:订阅事件 c#
评论