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

时间:2011-09-09 10:54:55

标签: java generics effective-java

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

帮助我了解SetSet<?>Set<Object>之间的区别?

以下段落摘自本书。

  

作为快速回顾,Set<Object>是表示a的参数化类型   可以包含任何类型对象的set,Set<?>是通配符类型   表示只能包含某些未知对象的集合   type,Set是一个原始类型,它选择了泛型类型   系统

“某种未知类型”是什么意思?是Object类型的所有未知类型?在这种情况下,Set<?>Set<Object>之间的具体差异是什么?

8 个答案:

答案 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或任何其他类型的列表。你选择哪一个?

  1. 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();
    }
    
  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();
    }