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

发布于 01-16 21:33 字数 905 浏览 1 评论 0原文

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

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

虽然通常由于类型擦除而无法在运行时检索类型,但由于反射实用程序和在字节码中存储关于泛型的元数据应该可以像这个答案中看到的那样<一href="https://stackoverflow.com/q/1901164/16720064">我有同样的问题。

接受的答案如下所示,

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

但对我不起作用,因为 .getActualTypeArguments()[0] 不是 ParameterizedType 的实例,而是 Class 从而导致演员阵容失败。

有什么方法可以可靠地做到这一点吗?我知道我可以手动传递 Class 对该方法的引用,但由于这是一个库,因此能够仅使用泛型类型会更加方便。

I am trying to retrieve the Class<?> representation of a type parameter at runtime like this:

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

While normally it is not possible to retrieve the types at runtime because of type erasure, due to reflection utilities and meta-data stored about generics in bytecode it should be possible like seen in this answer to the same question I have.

The accepted answer looks the following

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

but does not work for me, due to .getActualTypeArguments()[0] not being an instance of ParameterizedType but Class and thus failing the cast.

Is there any way to reliably do this? I know I can manually pass a Class<E> reference to the method but due to this being a library being able to just use the generic type would be much more convenient.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一口甜2025-01-23 21:33:34

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

我认为必须说,这不仅仅是一个“一阶”继承问题。如以下示例所示:

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 } 并且不包含任何对运行时类型 String 的引用。

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

(not a good enough reputation to answer, but:)

I think it must be said that it's not only a "first-order" inheritance issue. As the following example shows:

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();

The above example will print Type[1] { T } and doesn't contain any reference to the runtime-type String.

On JShell (haven't investigated a "standard" runtime), the type of g is a lambda whose direct super type is Object. I haven't found type parameters on it.

爱本泡沫多脆弱2025-01-23 21:33:34

之所以有效,

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

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

但是,在您的情况下,传递给 onCatch 参数的任何内容都不会具有 Consumer 的超类。毕竟,Consumer 不是一个类!您需要使用 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) {

    }
});

匿名类将实现 Consumer,并且此信息将被写入表示匿名类的类文件中。

但是,如果您使用 lambda:

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

则仅在与调用者相同的类中生成这样的方法:

private static void lambda$caller$0(RuntimeException e) {

}

并且在运行时, invokedynamic 用于创建一个实现 Consumer 的实例,这是个坏消息:无论出于何种原因,类型参数 RuntimeException 都不是为此实例生成的类的一部分。

现在找到 RuntimeException 的唯一方法就是知道调用者是谁,找到 lambda$caller$0 方法并查看其参数。

也就是说,到目前为止我所写的所有内容几乎都是实现细节,并且我不会在生产代码中使用其中的任何内容。我想说你应该只添加一个 Class 参数:无论如何,

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

它在调用者方面看起来并没有什么不同。

The reason why

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

works was because the superclass of this happens to be a class with a parameterised type as its superclass. Therefore you can get the actual type arguments of that type. The type parameters of superclasses are stored as metadata in the class file if you write them in the source file.

However, in your case, whatever is passed to the onCatch parameter is not going to have a superclass of Consumer<T>. After all, Consumer<T> is not a class! You need to use getGenericInterfaces and find the one that has the name that starts with 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]
 );

This would work if the caller calls onCatch like this:

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

    }
});

The anonymous class will implement Consumer<RuntimeException>, and this information will be written to the class file representing the anonymous class.

However, if you use a lambda:

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

Then only a method like this is generated in the same class as the caller:

private static void lambda$caller$0(RuntimeException e) {

}

and at runtime, invokedynamic is used to create an instance that implements Consumer, and this is the bad news: the type parameter RuntimeException is not part of the generated class for this instance, for whatever reason.

The only way you can find RuntimeException now then, is if you somehow know who the caller is, and find the lambda$caller$0 method, and look at its parameter.

That said, everything I've wrote so far is pretty much all implementation detail, and I wouldn't use any of that in production code. I would say you should just add a Class<E> parameter:

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

It doesn't look that different on the caller's side anyway.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文