原始类型,无界外卡和generics中使用Object之间的区别是什么

我正在阅读有效Java中关于generics的章节。

帮我理解SetSetSet之间的区别?

以下段落摘自本书。

作为快速回顾, Set是一个参数化类型,表示可以包含任何类型对象的SetSet是一个通配符类型,表示一个只能包含某些未知类型的对象的Set ,而Set是一个原始类型type,选择退出generics类型系统。

什么是“某种未知类型”? 是所有未知类型的Object类型? 在这种情况下, SetSet之间的具体区别是什么?

  • 原始类型( Set )将类型视为根本没有generics类型信息 。 请注意细微的效果,不仅会忽略类型参数T还会忽略该类型的方法可能具有的所有其他类型参数。 您可以向其添加任何值,它将始终返回Object
  • Set是一个接受所有Object对象(即所有对象)的Set ,它将返回Object类型的Object
  • Set是一个Set ,它接受某些特定但未知类型的所有对象,并将返回该类型的对象。 由于对此类型一无所知 ,因此无法向该集合添加任何内容( null除外),并且您唯一知道它返回的值是它们是Object某个子类型。

在运行时,由于类型擦除,JVM将只看到Set

在编译时,有一个区别:

因此, Set参数Set.add(E element)带有Object的类型E ,将Set.add(E element)参数Set.add(Object element)

另一方面, Set ,在类型E上添加通配符,使Set.add(E element)转换为Set.add(? element) 。 由于这不可编译,因此java将其“翻译”为Set.add(null element) 。 这意味着您无法向该集添加任何内容(null除外)。 原因是通配符引用了未知类型。

什么是“一些未知类型”的意思

究竟是什么意思 – Set一些通用参数,但我们不知道它是什么。

因此,分配给Set变量的Set可能是Set ,或Set ,或Set>或包含任何其他特定类型的集合。

那么这对你如何使用它意味着什么呢? 那么,你从中获得的任何东西都将是一个实例? 无论那是什么。 由于我们不知道类型参数是什么,所以你不能说具体的东西比集合的元素可以赋值给Object (只是因为所有类都从它扩展)。

如果你想在集合中添加一些东西 – 那么, add方法需要一个? (这是有道理的,因为这是集合中的对象类型)。 但是,如果您尝试添加任何特定对象,您如何确定这是类型安全的? 你不能 – 如果你正在插入一个String,你可能会把它放入一个Set中,这会打破你从generics中获得的类型安全性。 因此,虽然您不知道generics参数的类型,但您不能提供此类型的任何参数(使用null的唯一例外,因为它是任何类型的“实例”)。


与大多数与generics相关的答案一样,这一点集中在集合上,因为它们本能地更易于理解。 但是,参数适用于任何采用generics参数的类 – 如果使用无界通配符参数声明它? ,您不能向它提供任何参数,并且您收到的该类型的任何值只能分配给Object

Set :这里没有generics,不安全。 添加你想要的。

Set :我们的范围中我们不知道的某种类型的集合。 与Set相同 Set 。 可以引用任何类型的集合,但必须在实际实例化集合的位置定义该类型。 使用通配符引用,我们无法修改集合(我们不能添加或删除任何非null)。 就像一个观点。

Set :包含对象的集合(仅基类,而不是子类)。 我的意思是你可以使用类型为Object的集合来实例化集合,例如HashSet但不能使用HashSet 。 您当然可以将任何类型的元素添加到集合中,但仅仅因为它发生的一切都是Object或Object的子类。 如果将集合定义为Set,则只能添加Number的Numbers和子类,仅此而已。

SetSet之间的区别在于Set Set类型的变量可以为其分配更具体的generics类型,如:

 Set set = new HashSet(); 

Set只能分配Set

 Set set = new HashSet(); // won't compile 

Set仍然有用,因为任何对象都可以放入其中。 它就像那个意义上的原始Set ,但在generics类型系统中效果更好。

我正在向我的朋友解释这个项目,并特别要求“safeAdd”方法作为unsafeAdd示例的计数器。 所以在这里。

 public static void main(String[] args) { List strings = new ArrayList(); unsafeAdd(strings, new Integer(42)); // No compile time exceptions // New safeAdd(strings, new Integer(42)); // Throwing an exception at compile time String s = strings.get(0); // Compiler-generated cast } private static void unsafeAdd(List list, Object o) { list.add(o); } private static  void safeAdd(List list, E o) { list.add(o); } 
 Set set = new HashSet(); set.add(new Object()); // compile time error 

由于我们不知道集合的元素类型代表什么,我们无法向其添加对象。 add()方法接受类型为E参数,即Set的元素类型。 当实际的类型参数是? ,它代表一些未知的类型。 我们传递给add的任何参数都必须是这种未知类型的子类型。 因为我们不知道它是什么类型,所以我们无法传递任何内容。唯一的例外是null ,它是每种类型的成员。

给定Set ,我们可以调用get()并使用结果。 结果类型是未知类型,但我们始终知道它是一个对象。 因此,可以安全地将get()的结果赋给Object类型的变量,或者将其作为期望Object类型的参数传递。

假设您正在编写一种常用方法来打印出现在List中的元素。 现在,此方法可用于打印Integer,Double,Object或任何其他类型的列表。 你选择哪一个?

  1. List :如果我们使用这个,这将有助于我们只打印Object类型的元素。 它不适用于打印属于Double等其他类的元素。 这是因为Generic默认情况下不支持inheritance,需要使用’super’关键字明确指定。

     // Would only print objects of type 'Object' public static void printList(List list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); } 
  2. List :这可以帮助我们有一个打印任何数据类型的常用方法。 我们可以使用此方法打印任何类型的实例。

     // The type would really depend on what is being passed public static void printList(List list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); }