原始类型与泛型中的<?>有什么区别?

10

也许有人问过,但我找不到了。

请告诉我以下两者的确切区别:

ArrayList list = new ArrayList();

并且

ArrayList<?> list = new ArrayList();

我不确定这两者之间的确切区别是什么。
谢谢...

https://dev59.com/G2_Xa4cB1Zd3GeqP6ONt - assylias
5个回答

7
ArrayList<?> 的意思是“任何类型”。换句话说,任何类型的 ArrayList 都可以分配给这样的变量。这可能是 ArrayList<Integers>ArrayList<JButton> 或其他任何类型。仅使用通配符,而不使用关键字 super(后跟类型),意味着您不能向定义为 ArrayList<?> 的列表添加任何内容。但是,仅使用 ArrayList 意味着旧式无类型的 ArrayList,您可以执行各种操作,包括 add
List<?> list;
List<Integer> ints = new ArrayList<Integer>();
List<Integer> strings = new ArrayList<Integer>();
list = ints; // valid
list = strings; // valid
list.add("new"); // compile error

更新:

假设我有以下方法:

void insert(List list) {
   // loop through list, do whatever you like
   list.add("my string"); // dangerous operation 
}

现在,如果我调用insert(ints),编译器会生成一个警告,但不会阻止我将字符串添加到整数列表中。将方法更改为以下内容:
void insert(List<?> list) {
   // loop through list, do whatever you like
   list.add("my string"); // compiler error on this dangerous operation
}

这会阻止我执行此操作。


1
你说得对。但它们之间的行为差异是什么?如果唯一的区别是我们不能向ArrayList<?>中添加任何内容,那么在什么情况下我们需要它呢? - Chintan Patel
2
这可以用作只读列表。假设您需要将不同的列表传递给仅对它们执行读取操作的函数。使用此语法,您可以这样做。但是,如果将集合作为旧的无类型集合传递,则可能对集合执行某些非法操作。如果您将泛型集合用作非泛型集合,则编译器会警告您。使用 <?> 语法,编译器将保护您的列表(请参见我的编辑)。 - Ean V
1
你没有回答他的问题。他在问有一个带通配符的列表和另一个没有泛型的原始对象列表之间有什么区别。 - Enrico Giurin

4
ArrayList list = new ArrayList();

我们声明了一个可以接受任何类型对象的数组列表。
例如:
list.add(new Dog());
list.add(new Person());
list.add("Test");

对于 ArrayList<?> list = new ArrayList();

我们使用泛型声明了一个可以接受任何对象的数组列表,使用通配符?

关键在于我们无法向该数组列表中添加元素。

这段代码甚至无法编译:

ArrayList<?> list = new ArrayList();
list.add("test");

更新:

我认为在泛型中,“?”通配符的唯一目的是通过“extends”关键字进行耦合。

ArrayList<? extends Animal> list = new ArrayList<Dog>();

在这种情况下,我们将任何继承自Animal对象的list添加到其中,或作为参数传递给方法。
public void foo(List<?> list) { }

在这种情况下,方法foo无法向参数list中添加对象。

也许你应该解释一下?的含义,这是导致编译错误的原因(我猜他们不会只为了让代码产生编译错误而添加这个特性)。 - SJuan76
它主要用于函数调用签名。这是一个非常复杂的主题;PECS。http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) - seand
在foo()函数内部,你不能添加对象,但是你可以遍历列表,例如:for(Object object:list){ System.out.println(object); } - Enrico Giurin

1
ArrayList list = new ArrayList();

这是不带参数的容器,早于Java泛型。通常需要进行强制类型转换才能获得所需对象。
ArrayList<String> list = new ArrayList<String>();

在这里,我们指定容器保存的是字符串对象。读取内容时不需要进行转换。

<?>

通配符参数表示“某些东西”,例如String、Integer等。
请注意,ArrayList<?> list = new ArrayList()是无效的语法;通常通配符会用在方法参数等地方。


它不是无效的,但将泛型与原始类型混合使用。 - assylias

1
行为上没有区别。
真正的区别在于编译器对它们的处理方式。在第一种情况下,你告诉编译器“将其视为原始类型”,不要进行任何通用静态类型化。在第二种情况下,你是在说“将其视为通用类型”...但实际类型参数是“我们想要避免在这里指定的某种类型”。
请注意,“”通配符语法不能用于需要确定类型的地方。
@SJuan76评论道:
“(我猜他们添加这个功能不仅仅是为了让一些代码产生编译错误)”
实际上,你可以说他们确实这样做了。更准确地说,他们保留了旧形式,以便旧的(Java 5之前)代码可以继续使用Java 5+编译器而不会产生编译错误。

嗯,有所不同。如果从类中剥离所有通用信息,可能会导致绑定到不同方法的可能性。虽然我还没有检查ArrayLust是否具有可以以不同方式绑定的方法。 - Bohemian

0

ArrayList < ? > 表示类型未知的数组列表,它被称为通配符类型。

使用通配符,会发生以下情况

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

没有通配符,您可以向数组中添加任何内容。

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