我正在阅读有效Java中关于泛型的章节。
帮助我了解Set
,Set<?>
和Set<Object>
之间的区别?
以下段落摘自本书。
作为快速回顾,
Set<Object>
是表示a的参数化类型 可以包含任何类型对象的set,Set<?>
是通配符类型 表示只能包含某些未知对象的集合 type,Set
是一个原始类型,它选择了泛型类型 系统
“某种未知类型”是什么意思?是Object
类型的所有未知类型?在这种情况下,Set<?>
和Set<Object>
之间的具体差异是什么?
答案 0 :(得分:20)
Set
)将该类型视为根本没有通用类型信息。请注意细微的效果,不仅会忽略类型参数T
,还会忽略该类型的方法可能具有的所有其他类型参数。您可以向其添加任何值,它将始终返回Object
。Set<Object>
是Set
,它接受所有Object
个对象(即所有对象)并返回Object
类型的对象。Set<?>
是Set
,它接受某些特定但未知的类型的所有对象,并将返回该类型的对象。由于没有了解此类型,因此您无法向该集合添加任何内容(null
除外)并且您唯一知道的值它返回的是它们是Object
的一些子类型。答案 1 :(得分:3)
在运行时,由于类型擦除,JVM只会看到Set
。
在编译时,存在差异:
Set<Object>
使用E
参数化了Object
类型,因此Set.add(E element)
将参数化为Set.add(Object element)
。
Set<?>
在类型E
上添加通配符,以便将Set.add(E element)
翻译为Set.add(? element)
。由于这不可编译,因此java将其“翻译”为Set.add(null element)
。这意味着您无法向该集添加任何内容(null除外)。原因是通配符引用了未知类型。
答案 2 :(得分:3)
“某种未知类型”是什么意思
究竟是什么意思 - Set
有一些泛型参数,但我们不知道它是什么。
因此,分配给Set<?>
变量的集可能是Set<String>
,Set<Integer>
,Set<Map<Integer, Employee>>
或包含任何其他特定类型的集合。
那么对于如何使用它意味着什么呢?好吧,你得到的任何东西都是?
的一个实例,不管是什么。由于我们不知道类型参数是什么,因此您不能说具体的内容,因为该集合的元素可以分配给Object
(仅因为所有类都从它扩展)。
如果你想在集合中添加一些东西 - 那么,add
方法需要?
(这是有道理的,因为这是集合中的对象类型)。但是,如果您尝试添加任何特定对象,您如何确定这是类型安全的?你不能 - 如果你插入一个字符串,你可能会把它放到Set<Integer>
中,这会打破你从泛型中获得的类型安全性。因此,虽然您不知道泛型参数的类型,但您不能提供此类型的任何参数(null
的唯一例外,因为它是任何类型的“实例”)。
与大多数与仿制药相关的答案一样,这主要集中在收藏品上,因为它们本能地更易于理解。但是,参数适用于任何采用泛型参数的类 - 如果使用无界通配符参数?
声明它,则不能提供任何参数,以及的任何值接收该类型只能分配给Object
。
答案 3 :(得分:2)
Set
:此处没有仿制药,不安全。添加你想要的东西。
Set<?>
:我们在范围内不知道的某种类型的集合。与Set<? extends Object>
相同。可以引用任何类型的集合,但必须在实际实例化集合的位置定义该类型。使用通配符引用,我们无法修改集合(我们无法添加或删除任何非null)。就像一个观点。
Set<Object>
:设置包含对象(仅基类,而不是子类)。我的意思是你可以使用类型为Object的集合来实例化集合,例如HashSet<Object>
但不能使用HashSet<String>
。您当然可以将任何类型的元素添加到集合中,但仅仅因为它发生的一切都是Object或Object的子类。如果集合被定义为Set,则只能添加Number的Numbers和子类,而不能再添加。
答案 4 :(得分:1)
Set<Object>
和Set<?>
之间的区别在于Set<?>
类型的变量可以为其分配更具体的泛型类型,如:
Set<?> set = new HashSet<Integer>();
Set<Object>
只能分配Set<Object>
:
Set<Object> set = new HashSet<Integer>(); // won't compile
Set<Object>
仍然有用,因为任何对象都可以放入其中。它就像那个意义上的原始Set
,但在泛型类型系统中效果更好。
答案 5 :(得分:1)
Set<?> set = new HashSet<String>();
set.add(new Object()); // compile time error
由于我们不知道集合的元素类型代表什么,我们无法添加对象
它。 add()
方法接受E
类型的参数,Set
的元素类型。
当实际类型参数为?
时,它代表某种未知类型。任何
我们传递给add的参数必须是这种未知类型的子类型。因为我们
不知道是什么类型,我们不能传递任何东西。唯一的例外是
null
,是所有类型的成员。
给定Set<?>
,我们可以调用get()
并使用结果。结果类型是
未知类型,但我们总是知道它是一个对象。因此安全
将get()
的结果分配给类型为Object
的变量,或将其作为参数传递
预期类型为Object
的地方。
答案 6 :(得分:1)
我正在向我的朋友解释这个项目,并特别要求“safeAdd”方法作为unsafeAdd示例的计数器。所以就是这样。
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
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 <E> void safeAdd(List<E> list, E o) {
list.add(o);
}
答案 7 :(得分:-1)
假设您正在编写一种常用方法来打印出现在List中的元素。现在,此方法可用于打印Integer,Double,Object或任何其他类型的列表。你选择哪一个?
List<Object>
:如果我们使用这个,这将有助于我们只打印Object类型的元素。它对于打印属于Double等其他类的元素不会有用。这是因为Generic默认情况下不支持继承,需要使用'super'关键字明确指定。
// Would only print objects of type 'Object'
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
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();
}