获取 Class-Java 中泛型参数的对象表示

Get Class-object representation of generic parameter in Java

我正在尝试在运行时检索类型参数的 Class<?> 表示,如下所示:

public <E extends Exception> Try<T> onCatch (Consumer<E> onCatch) { 
    // retrieve `Class<E>`
}

虽然通常由于类型擦除而无法在运行时检索类型,但由于反射实用程序和存储在字节码中的关于泛型的元数据,它应该是可能的在 this answer to the same question 看过。

接受的答案如下所示

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

但对我不起作用,因为 .getActualTypeArguments()[0] 不是 ParameterizedType 的实例而是 Class,因此转换失败。

有没有办法可靠地做到这一点?我知道我可以手动将 Class<E> 引用传递给该方法,但由于这是一个能够仅使用泛型类型的库,因此会方便得多。

原因

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

之所以可行是因为 this 的 superclass 恰好是一个 class,其 superclass 的参数化类型。因此,您可以获得该类型的实际类型参数。如果将 superclasses 的类型参数写入源文件,它们将作为元数据存储在 class 文件中。

但是,在您的情况下,传递给 onCatch 参数的任何内容都不会具有 Consumer<T> 的超级 class。毕竟Consumer<T>不是class!您需要使用 getGenericInterfaces 并找到名称以 java.util.function.Consumer.

开头的那个
System.out.println(
    // I've assumed the Consumer interface is the first one, to keep it brief
    ((ParameterizedType)onCatch.getClass().getGenericInterfaces()[0])
        .getActualTypeArguments()[0]
 );

如果调用者这样调用 onCatch,这将起作用:

onCatch(new Consumer<RuntimeException>() {
    @Override
    public void accept(RuntimeException e) {

    }
});

匿名class将执行Consumer<RuntimeException>,此信息将写入代表匿名class的class文件。

但是,如果您使用 lambda:

onCatch((RuntimeException e) -> {});

然后只在与调用者相同的class中生成这样的方法:

private static void lambda$caller[=14=](RuntimeException e) {

}

并且在运行时,invokedynamic is used to create an instance that implements Consumer,这是个坏消息:无论出于何种原因,类型参数 RuntimeException 都不是此实例生成的 class 的一部分。

你现在能找到 RuntimeException 的唯一方法是,如果你知道调用者是谁,然后找到 lambda$caller[=28=] 方法,然后查看它的参数。

就是说,到目前为止我写的所有内容几乎都是实现细节,我不会在生产代码中使用任何这些细节。我会说你应该只添加一个 Class<E> 参数:

onCatch(RuntimeException.class, e -> {});

不管怎样,在调用方这边看起来并没有什么不同。

(信誉不够好,无法回答,但是:)

我认为必须说这不仅仅是一个“first-order”的继承问题。如下例所示:

abstract class Lifted<T> implements Consumer<T> { public abstract void accept(T x); }

<T> Consumer<T> lift(Consumer<T> f) { return new Lifted<T>() { @Override public void accept(T x) { f.accept(x);}};}

Consumer<String> g = System.out::println;
Consumer<String> c = lift(g);

((java.lang.reflect.ParameterizedType) c.getClass().getGenericSuperclass()).getActualTypeArguments();

以上示例将打印 Type[1] { T } 并且不包含对 runtime-type String.

的任何引用

在 JShell(尚未研究“标准”运行时)上,g 的类型是一个直接超类型为 Object 的 lambda。我没有在上面找到类型参数。