这三个参数化变量有何不同?

6

假设有以下声明的AGenericClass:

public class AGenericClass<T> {
  T subject;
  public void setSubject(T subject) {
    this.subject = subject;
  }
}
abc变量之间有什么不同?
AGenericClass<String> a = new AGenericClass<>();
AGenericClass<?> b = new AGenericClass<String>();
AGenericClass c = new AGenericClass<String>();

a.setSubject("L"); // OK.

b.setSubject("M"); // Error: setSubject(capture<?>) cannot be
                   // applied to (java.lang.String)

c.setSubject("N"); // Warning: Unchecked call to 'setSubject(T)'
                   // as a member of raw type 'AGenericClass'
abc 在 IDE 中都能够正常声明,但是当调用 setSubject 时它们的行为却不同。
3个回答

3
三者之间的差异主要与编译时获得的检查有关。鉴于泛型旨在保护用户免受运行时不安全转换的影响,它们之间的重要差异和关键点都源于此。
AGenericClass<String> 声明了一个特定类型为 String 的泛型类实例。对于这个泛型所需的任何参数,都将绑定到 String 上,并且会在编译时执行类型检查和类型安全性。
换句话说,如果你有 AGenericClass<String> a = new AGenericClass<>(); 并尝试调用 a.setSubject(3),Java 将不允许应用程序编译,因为类型不匹配。 AGenericClass<?> 声明了一个具有未知类型的泛型类实例。形式上,? 是一个通配符,它可以是 任何 类型,如果你想从中检索元素,那么这样做是可以的,但是如果你想向其中添加元素,就不行
原因是什么?AGenericClass<?>实际上是AGenericClass<? extends Object>,这很重要,因为涉及到输入和输出原则。通常(虽然这并不是严格的保证),带有extends的任何泛型都意味着只读操作。
正如我之前所说,从中读取是可以的,因为你保证了一个最多是Object的类,但是你不能写入它,因为你不知道,也无法保证从任何给定的调用中添加到它的对象的种类。
没有类型声明的AGenericClass是原始类型。您可以在此处阅读更多信息,但要知道它们存在是为了向后兼容。如果您的代码使用原始类型,您将失去编译时检查,并且您的代码可能会在运行时抛出ClassCastException,这更难以调试、诊断和解决。

2
AGenericClass<String> a = new AGenericClass<>();

所谓“钻石”泛型实例化。当你在泛型类型的构造函数中设置空的<>时,编译器会尝试从上下文中确定类型。在这个例子中,编译器将使用<String>,因为a声明为<String>
AGenericClass<?> b = new AGenericClass<String>();

这个叫做“无限制通配符”。b 将被创建为 未知类型的泛型。当你尝试使用 setSubject() 时,编译器无法检查参数的类型。这就是为什么会出现异常的原因。要解决这个问题,你可以创建一个带有定义泛型类型的辅助方法。
AGenericClass c = new AGenericClass<String>();

在这种情况下,c 声明为 "原始类型"。 c 将作为具有 <Object> 参数的泛型创建。因为您可以将 String 分配给 Object,所以方法 setSubject() 将使用 String。实际上,它将与任何继承自 Object 的类型一起工作,并且潜在地可能会导致错误。编译器会对此发出警告。

1
AGenericClass<String> a = new AGenericClass<>();

表示这是一个String类型的AGenericClass,并将在类型参数T的位置上接受String作为参数。因此,当您调用a.setSubject("L");时,它不会抱怨。

AGenericClass<?> b = new AGenericClass<String>();

?表示它是无界通配符,意味着类型未指定或未知。

这只能保证返回类型将是Object,如果你有一个类似public T getSubject()的方法。你可以将其分配给类型为Object的变量或将其作为参数传递,其中期望类型为Object。无法执行设置操作。extendssuper是两个关键字,您可以与?一起使用以设置可能需要进一步探索的下限/上限。

AGenericClass c = new AGenericClass<String>();

Warning: Unchecked call to 'setSubject(T)'.. 表示此调用正在以原始类型进行,因此即使您调用 setSubject(new Integer(1)),它也不会抱怨,即它不执行任何类型安全检查。但是随后当您尝试获取此主题并将其转换为字符串时,您会遇到 ClassCastException,因为您忽略了编译器警告并且我们没有获得泛型提供的类型安全性。


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