获取 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。我没有在上面找到类型参数。
我正在尝试在运行时检索类型参数的 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。我没有在上面找到类型参数。