Java泛型:在运行时是否保留有关泛型类型的任何元信息?

4

背景

我对Java泛型的理解是它完全是一个编译时特性(主要关注类型安全检查)。任何泛型类的类型信息在运行时都会丢失(类型擦除)。

尽管如此,我看到许多框架似乎也在运行时利用类型信息。例如,Google Guice Providers。Guice Provider可以在运行时实例化并提供其泛型类型的新实例。

class Container
{
     @Inject   
     public Container(Provider<Content> contentProvider)
     {
     //This works at Runtime... but How ??? 
     //When type's are not even preserved at runtime, how does the Provider knows it has to instantiate an object of type 'Content' 
     Content content = contentProvider.get();   
     }
}

问题

  1. 在运行时,是否还保留与通用类型相关的信息?如果是,是什么?如果不是,那么像 Google Guice 这样的库是如何在内部操作的(上面的例子)?

  2. 泛型除了编译时安全性以外,还有更多的用途吗?比如说,在确保编译时安全性的情况下,是否有其他场景可以使用泛型并获得优势?


泛型除了编译时的安全性还有其他的特点吗?不,泛型只是强制类型转换的简化。 - Andy Turner
2
泛型只作为编译时功能,但是很多泛型信息仍然可以通过反射在运行时访问。 - shmosel
有给我点踩的人可以评论一下吗? - Harshit
6个回答

4
如果一个类扩展了一个泛型类或接口,并为参数提供了一个具体的类型,那么该类型可通过Class.getGenericSuperclass()获得。在这种情况下,该方法将返回一个ParameterizedType,其中包含实际的参数化信息。
例如,如果您有以下内容:
class BigIntegerList extends ArrayList<BigInteger> {}

然后你可以这样做:

Class<BigIntegerList> fooClass = BigIntegerList.class;
Type superclass = fooClass.getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
  ParameterizedType parameterized = (ParameterizedType) superclass;
  Type[] parameterizations = parameterized.getActualTypeArguments();
  System.out.println(Arrays.toString(parameterizations));
  // prints: "[class java.math.BigInteger]"
}

这确实被反射密集型库(如Guice)所使用。另一个例子是Jackson的TypeReference,它可以让您将JSON列表转换为特定类型(例如:列表中每个元素都是BigDecimal类型)。


有趣。会仔细了解一下。谢谢。 - Harshit
你似乎正在使用一个展示继承的例子(A继承B)。是否有可能以某种方式访问ArrayList <BigInteger>的实例并了解类型(在你提到的情况下是'BigInteger')? - Harshit
@Harshit 就像这样 List<BigInteger> myBigInts = new ArrayList<BigInteger>() 吗?如果是这种情况,答案是否定的。那就是类型擦除的问题。 - yshavit

2
当然,支持表示类是泛型的信息。
换句话说:当你反编译ArrayList.class时,你会发现有关这个类允许一个泛型类型参数的提示。换句话说:类文件包含元信息。并且使用反射可以在运行时检查这些元信息。
但是,当你有另一个使用一些List对象的类时 - 除非你使用一些特定的模式,否则你不会在已编译的类中找到关于“列表使用Integer”的信息,例如在这里概述的here
因此,答案基本上是:对于几乎所有实际相关的用例,“泛型”仅在编译时存在。
示例:
public class GenericsExample<T> {
  private T member;   
  public T foo(T bar) {
     return member;
  }
}

现在运行:javap -p -c GenericsExample
Compiled from "GenericsExample.java"
public class GenericsExample<T> {
  private T member;

  public GenericsExample();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public T foo(T);
    Code:
       0: aload_0       
       1: getfield      #2                  // Field member:Ljava/lang/Object;
       4: areturn       
}

正如您所看到的,反编译器理解该类使用了泛型类型T。更多详情请参见这里那里

有任何人对投票者的评论吗?OP问是否泛型仅在编译时存在 - 我表达了关于泛型的META信息是类文件的一部分 - 因此也可以在运行时访问。 - GhostCat
不是下投票者,但你确实可以从字段和参数中获取具体的通用信息。 - shmosel
@shmosel,你说得对 - 我已经相应地更新了我的答案。 - GhostCat
我认为有些不爽的人浏览了这个问题并对所有内容进行了负评。 - yshavit
你能否举个例子来说明一下? - Harshit
1
@Harshit 请查看我的更新。请不要忘记在某个时候接受其中一个答案。 - GhostCat

0

如果您不知道要在特定类中使用哪种类型,泛型是一种很好的编程方式。在运行时,泛型类类型将根据类的输入进行设置。它主要用于编译时安全性。


0
  1. Java泛型使用所谓的类型擦除,因此在运行时没有关于类型的信息可用。但是,如果可以以某种方式传递类型信息(实际上,这是创建通用数组的唯一方法),则可以使用Class.newInstance()方法创建任何类的实例。

  2. 编译时安全性是泛型的主要目标。但是,它们经常用于编写更简洁的代码,否则可能无法实现。

对于详细的处理,我推荐优秀的书籍Java Generics and Collections


0
在运行时是否有与通用类型相关的信息得以保留?如果有,是什么?
已编译类中保存的单个信息是获得擦除后的原始对象/变量转换为源代码中用作通用类型的特定类型之后进行的类型转换。但这仅依赖于变量的声明类型,而不是实际使用的通用类型。
因此,在运行时,您无法直接访问通用信息,除非通过在实例化通用类时传递一个类的方式来解决。
如果没有,那么像Google Guice这样的库如何在内部操作?
您是错误的。在Guice中,以下代码:
 Content content = contentProvider.get();   

将返回Content的实例,而不是泛型类型。 请查看文档

T get()

提供T的实例。


将返回Content的实例,而不是通用类型。请查看文档:这就是我试图表达的。在这里,Content是类型(在Provider<Content>中)。由于它的类型会被擦除,Provider如何能够在运行时实例化'Content'呢? - Harshit
啊,好的。我明白你的问题了。当实现 Provider<T> 时,它必须提供一个泛型类型,例如 Provider<Content>。因此为了编译正常,它必须以这种方式实现 get()Provider get()。因此该方法在编译后的类中得到实现。它不会关心擦除操作,因为这已经在之前完成了。 - davidxxx

-2

通用信息只在编译时持久化。就像您的例子一样,它只在编译时可用。让我举个例子来解释一下

公共无效打印对象(列表empt){

//这并不表明列表在运行时保留了它将返回什么类型的对象的信息。 Employment en = empt.get(); }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接