Java 是否可以在运行时获取lambda参数的类型?
我在应用Mockito的参数匹配器时遇到了类型安全问题 给定以下接口:Java 是否可以在运行时获取lambda参数的类型?,java,generics,lambda,java-8,mockito,Java,Generics,Lambda,Java 8,Mockito,我在应用Mockito的参数匹配器时遇到了类型安全问题 给定以下接口: interface SomeInterface { int method(Object x); } 我试图模拟它的唯一方法,并使用与matcher类型不同的参数调用它: SomeInterface someInterface = mock(SomeInterface.class); when(someInterface.method(argThat((ArgumentMatcher<Integer>
interface SomeInterface {
int method(Object x);
}
我试图模拟它的唯一方法,并使用与matcher类型不同的参数调用它:
SomeInterface someInterface = mock(SomeInterface.class);
when(someInterface.method(argThat((ArgumentMatcher<Integer>) integer -> integer == 42))).thenReturn(42);
someInterface.method("X"); // Throws ClassCastException
但是,当我将lambda扩展到匿名类时,一切正常:
SomeInterface someInterface = mock(SomeInterface.class);
when(someInterface.method(argThat(new ArgumentMatcher<Integer>() {
@Override
public boolean matches(Integer integer) {
return integer == 42;
}
}))).thenReturn(42);
someInterface.method("X"); // OK, method invokes normally
我的第一个反应是“只使用intThati->i==42”,但很明显,这个实现正在删除它有一个ArgumentMatcher的信息,然后依赖于同样的反射方法,这是行不通的 您无法以反射的方式获取lambda参数类型,现有的Q&A解释了为什么它不可能,甚至不是预期的。请注意,这甚至不是lambda特定的。在一些普通类的场景中,类型也不可用 最后,当它需要在使用端进行额外的声明(如显式类型转换ArgumentMatcher)时,尝试使这种自动性工作是没有意义的。例如,代替
argThat((ArgumentMatcher<Integer>) integer -> integer == 42)
甚至
argThat(Integer.valueOf(42)::equals)
然而,当我们在这些琐碎的例子中,eq42也可以,甚至当someInterface.method42.thenReturn42
但是,当您经常需要在这样的上下文中使用更广泛的参数类型来匹配复杂的整数表达式时,一个接近您最初尝试的解决方案是声明一个可重用的固定类型匹配器接口
interface IntArgMatcher extends ArgumentMatcher<Integer> {
@Override boolean matches(Integer arg0);
}
i==42作为更复杂表达式的占位符我的第一反应是“只使用intThati->i==42”,但很明显,该实现正在删除它有一个ArgumentMatcher的信息,然后依赖于不起作用的相同反射方法 您无法以反射的方式获取lambda参数类型,现有的Q&A解释了为什么它不可能,甚至不是预期的。请注意,这甚至不是lambda特定的。在一些普通类的场景中,类型也不可用 最后,当它需要在使用端进行额外的声明(如显式类型转换ArgumentMatcher)时,尝试使这种自动性工作是没有意义的。例如,代替
argThat((ArgumentMatcher<Integer>) integer -> integer == 42)
甚至
argThat(Integer.valueOf(42)::equals)
然而,当我们在这些琐碎的例子中,eq42也可以,甚至当someInterface.method42.thenReturn42
但是,当您经常需要在这样的上下文中使用更广泛的参数类型来匹配复杂的整数表达式时,一个接近您最初尝试的解决方案是声明一个可重用的固定类型匹配器接口
interface IntArgMatcher extends ArgumentMatcher<Integer> {
@Override boolean matches(Integer arg0);
}
i==42作为一个更复杂表达式的占位符@Holger的答案是100%正确的,但是,我自己也在为一个类似的问题而挣扎,我想提供一些额外的想法 这个问题很好地证明了lambda表达式与匿名类是完全不同的,并且前者不仅仅是表示后者的较短方式 假设我们定义了一个确定函数的第一个实现接口的方法: 在非常简化的版本中,这种分派方法可以实现为:
@SafeVarargs
public final <T> T dispatch(Object dispatchObject, Function<?, T>... cases)
{
@SuppressWarnings("unchecked")
T result = Stream.of(cases)
.filter(function -> parameterType(function).isAssignableFrom(dispatchObject.getClass()))
.findFirst()
.map(function -> ((Function<Object, T>)function).apply(dispatchObject))
.orElseThrow(NoSuchElementException::new);
return result;
}
Class<?> parameterType(Function<?, ?> function)
{
ParameterizedType type = (ParameterizedType)function.getClass().getGenericInterfaces()[0];
Type parameterType = type.getActualTypeArguments()[0];
return (Class<?>)parameterType;
}
我们得到了预期的结果
number
boolean
然而,如果我们要跑
System.err.println(dispatcher.<String>dispatch(Math.PI, (Number n) -> "number", (Boolean b) -> "boolean"));
使用lambda表达式而不是匿名类,代码将失败,并出现ClassCastException:java.lang.Class无法转换为java.lang.reflect.ParameterizedType,因为lambda表达式实现了原始类型
这是一个适用于匿名类但不适用于lambda表达式的问题。@Holger的答案是100%正确的,但是,我自己也在努力解决类似的问题,我想提供一些额外的想法 这个问题很好地证明了lambda表达式与匿名类是完全不同的,并且前者不仅仅是表示后者的较短方式 假设我们定义了一个确定函数的第一个实现接口的方法: 在非常简化的版本中,这种分派方法可以实现为:
@SafeVarargs
public final <T> T dispatch(Object dispatchObject, Function<?, T>... cases)
{
@SuppressWarnings("unchecked")
T result = Stream.of(cases)
.filter(function -> parameterType(function).isAssignableFrom(dispatchObject.getClass()))
.findFirst()
.map(function -> ((Function<Object, T>)function).apply(dispatchObject))
.orElseThrow(NoSuchElementException::new);
return result;
}
Class<?> parameterType(Function<?, ?> function)
{
ParameterizedType type = (ParameterizedType)function.getClass().getGenericInterfaces()[0];
Type parameterType = type.getActualTypeArguments()[0];
return (Class<?>)parameterType;
}
我们得到了预期的结果
number
boolean
然而,如果我们要跑
System.err.println(dispatcher.<String>dispatch(Math.PI, (Number n) -> "number", (Boolean b) -> "boolean"));
使用lambda表达式而不是匿名类,代码将失败,并出现ClassCastException:java.lang.Class无法转换为java.lang.reflect.ParameterizedType,因为lambda表达式实现了原始类型
这是使用匿名类但不使用lambda表达式的方法之一
@SafeVarargs
public final <T> T dispatch(Object dispatchObject, Function<?, T>... cases)
{
@SuppressWarnings("unchecked")
T result = Stream.of(cases)
.filter(function -> parameterType(function).isAssignableFrom(dispatchObject.getClass()))
.findFirst()
.map(function -> ((Function<Object, T>)function).apply(dispatchObject))
.orElseThrow(NoSuchElementException::new);
return result;
}
Class<?> parameterType(Function<?, ?> function)
{
ParameterizedType type = (ParameterizedType)function.getClass().getGenericInterfaces()[0];
Type parameterType = type.getActualTypeArguments()[0];
return (Class<?>)parameterType;
}
System.err.println(getTypeOf(42));
System.err.println(getTypeOf(true));
number
boolean
System.err.println(dispatcher.<String>dispatch(Math.PI, (Number n) -> "number", (Boolean b) -> "boolean"));