【问题标题】:Resolving a method's return type using its argument types使用参数类型解析方法的返回类型
【发布时间】:2014-05-21 04:22:46
【问题描述】:

假设我有一个方法:

public class AwesomeClass {

    public <E> List<E> convertIterableToList(Iterable<E> iterable) {
        ...
    }

}

运行时,如何根据参数类型解析方法的返回类型?例如,我希望实现一个假设的方法resolveReturnType,它的行为在这个小(伪Java)单元测试中得到了展示:

Method method = AwesomeClass.class.getDeclaredMethod("convertIterableToList", Iterable.class);

Type argumentType = {{{Set<String>}}}; // Just pretend this syntax works. :)
Type expectedReturnType = {{{List<String>}}};
Type actualReturnType = resolveReturnType(method, argumentType);

assertEquals(expectedReturnType, actualReturnType);

到目前为止,我一直在尝试使用 Guava 的 TypeToken 类,但进展不大。

【问题讨论】:

  • 你看过@stackoverflow.com/questions/3941384/…吗?还有,既然知道了参数类型,就可以从参数类型中扣除返回值,为什么还要再检查返回类型呢?
  • @Smutje:我在这个问题上花了很多时间,但它似乎并没有解决这个问题。如果我理解正确,他们会尝试在运行时从 object 获取通用信息。我提供的代码只是一个单元测试,展示了我想要完成的工作。实际的生产代码不会知道预期的返回类型。生产代码将使用resolveReturnType 告诉它一个方法的返回类型是什么,给定它的参数类型。
  • 你看过TypeToken.resolveType(Type)的Javadoc吗?
  • 但是,再说一遍 - 你为什么要重新计算已知的东西?
  • @LouisWasserman:我查看了resolveType,我已经研究了一个晚上,但我无法让它按照我想要的方式解决。 :(

标签: java generics reflection guava


【解决方案1】:

因此,这实际上是可能的,前提是您拥有可用方法的参数的实际正式 Types。正如@JvR 所指出的,这在运行时一般是不可能的,但是如果(如您的示例中)您能够使用TypeToken 或类似的方式明确指定这些类型,它确实有效。

static Type resolveReturnType(Type classType, Method method, Type... argTypes) {
  // this should resolve any class-level type variables
  Type returnType = TypeToken.of(classType)
      .resolveType(method.getGenericReturnType()).getType();
  Type[] parameterTypes = method.getGenericParameterTypes();

  TypeResolver resolver = new TypeResolver();
  for (int i = 0; i < parameterTypes.length; i++) {
    @SuppressWarnings("unchecked") // necessary for getSupertype call to compile
    TypeToken<Object> paramType =
        (TypeToken<Object>) TypeToken.of(parameterTypes[i]);
    @SuppressWarnings("unchecked") // necessary for getSupertype call to compile
    TypeToken<Object> argType =
        (TypeToken<Object>) TypeToken.of(argTypes[i]);

    if (method.isVarArgs() && i == parameterTypes.length - 1) {
      // TODO
    } else {
      TypeToken<?> argTypeAsParamType =
          argType.getSupertype(paramType.getRawType());
      resolver = resolver.where(
          paramType.getType(), argTypeAsParamType.getType());
    }
  }

  return resolver.resolveType(returnType);
}

上面的代码有一些漏洞:例如,在给定参数类型String[] 的情况下,它无法正确解析E foo(E[] array) 的返回类型。当然,对于返回类型具有未在其参数类型中使用的类型变量的任何泛型方法,它也无济于事。我也没有尝试过其他各种东西,比如通配符。但是对于您的示例,它可以工作,并且除了方法声明的变量之外,它还处理类声明的类型变量(如果它是实例方法):

public class Foo<T> {

  public <E> Map<T, E> convertIterableToMap(Iterable<E> iterable) {
    return null;
  }

  public static void main(String[] args) throws Exception {
    Method method = Foo.class.getMethod("convertIterableToMap", Iterable.class);

    Type instanceType = new TypeToken<Foo<Integer>>() {}.getType();
    Type setOfString = new TypeToken<Set<String>>() {}.getType();

    // prints: java.util.Map<java.lang.Integer, java.lang.String>
    System.out.println(resolveReturnType(instanceType, method, setOfString));
  }
}

【讨论】:

  • 优秀简洁!我希望我也可以通过解决实例类型参数来奖励积分。我是如此专注于TypeToken.resolveType 方法,以至于我完全忽略了它的getSuperclass 方法。
  • 另一件不能正确处理的事情是参数中有一个类型变量的多个实例的情况:TypeResolver 在尝试将类型变量映射到一个实数时抛出一个异常再次键入,即使两次的实际类型相同。这可以通过捕获IllegalArgumentException 或(更烦人的)通过重新实现TypeResolver 来解决。
【解决方案2】:

简短的回答:你不能。

更长的答案:

&lt;E&gt; List&lt;E&gt; convertIterableToList(Iterable&lt;E&gt; iterable) 有一个类型E,它通常是不可具体化的。您可以检查提供的可迭代对象是否已在其类定义中修复了此类型 (1),这意味着您可以检索该类型并找出 E 在该特定类型中的含义调用。

但在一般情况下,运行时不会知道任何特定调用的 E 是什么。

(1)意思类似于class StringList implements List&lt;String&gt;,其中类型变量是固定的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多