Java 泛型: List,List < Object > ,List < ? >

能否有人尽可能详细地解释一下下列类型之间的区别?

List
List<Object>
List<?>

让我说得更具体一点,我想什么时候用

// 1
public void CanYouGiveMeAnAnswer(List l) { }


// 2
public void CanYouGiveMeAnAnswer(List<Object> l) { }


// 3
public void CanYouGiveMeAnAnswer(List<?> l) { }
48610 次浏览

我向你推荐优秀的 Java 泛型教程高级非专利药教程,这两款昇阳电脑都可以买到。另一个伟大的资源是 Java 泛型和集合书。

为了补充 Rob 提到的教程,这里有一本 wikibook 来解释这个主题:
Http://en.wikibooks.org/wiki/java_programming/generics


编辑:

  1. 对列表中的项目类型没有限制

  2. 列表中的项必须扩展 Object

  3. 通配符自己使用,所以它匹配任何东西

在这一点上我得出结论,几乎没有任何/没有任何区别,是不是太天真了?

最简单的解释不是“ RTFM”:

List

将生成大量的编译器警告,但大多等效于:

List<Object>

同时:

List<?>

基本上就是泛型的意思,但是你不知道泛型类型是什么。当你不能修改刚刚返回 List 的其他东西的返回类型时,它可以很好地去除编译器警告。它在形式上更有用:

List<? extends SomeOtherThing>

最简短的解释是: 第二个条目是一个可以包含任何类型的列表,您可以向其中添加对象:

List<Object>

您列出的第一个项被视为基本上等同于这个,除了您会得到编译器警告,因为它是一个“原始类型”。

List

第三个列表可以包含任何类型,但不能向其中添加任何内容:

List<?>

基本上,当您真正拥有一个可以包含任何对象的列表并且希望能够向列表添加元素时,可以使用第二种形式(List<Object>)。你使用第三种形式(List<?>)当你收到列表作为一个方法返回值,你将迭代列表,但从来没有添加任何东西到它从来没有使用第一种形式(List)在新的代码编译在 Java 5或更高版本。

我这样说: 虽然 ListList<Object>可以包含任何类型的对象,但是 List<?>包含未知类型的元素,但是一旦该类型被捕获,它只能包含该类型的元素。这就是为什么它是这三种类型中唯一的类型安全变体,因此通常更可取。

用我自己的话来说:

名单

将声明一个普通集合,可以保存任何类型,并且总是返回 Object。

列表 < 对象 >

将创建一个列表,该列表可以包含任何类型的对象,但只能获得另一个 列表 < 对象 >

例如,这不起作用;

List<Object> l = new ArrayList<String>();

当然,您可以添加任何东西,但只能拉对象。

List<Object> l = new ArrayList<Object>();


l.add( new Employee() );
l.add( new String() );


Object o = l.get( 0 );
Object o2 = l.get( 1 );

终于来了

列表 < ? >

将允许您分配任何类型,包括

List <?> l = new ArrayList();
List <?> l2 = new ArrayList<String>();

这将被称为 未知的集合,而且由于 未知的公分母是 Object,因此您将能够获取 Objects (巧合)

未知的重要性体现在它与子类一起使用时:

List<? extends Collection> l = new ArrayList<TreeSet>(); // compiles


List<? extends Collection> l = new ArrayList<String>(); // doesn't,
// because String is not part of *Collection* inheritance tree.

我希望使用 Collection 作为类型不会造成混淆,这是我脑海中唯一的一棵树。

这里的区别在于,l 是属于 收集层次结构的 不知道的集合。

我什么时候会想用

public void CanYouGiveMeAnAnswer( List l ){}

当你不能做所有的铸造自己。

我什么时候会想用

public void CanYouGiveMeAnAnswer( List l<Object> ){}

当您想要限制 List 的类型时。例如,这将是一个无效的参数。

 new ArrayList<String>();

我什么时候会想用

public void CanYouGiveMeAnAnswer( List l<?> ){}

基本上没有。

正如其他文章所指出的,您正在询问一个名为泛型的 Java 特性。在 C + + 中,这称为模板。Java 中的这个特性通常比 C + + 中的更容易使用。

让我来回答你们的问题(如果这个词对 OO 讨论来说不是一个淘气的词的话)。

在泛型之前,有像 Vector 这样的具体类。

Vector V = new Vector();

向量包含你给它们的任何对象。

V.add("This is an element");
V.add(new Integer(2));
v.add(new Hashtable());

它们通过将给它的所有值转换为一个 Object (所有 Java 类的根)来实现这一点。当您试图检索存储在 Vector 中的值时,需要将该值强制转换回 原创的类(如果您想对它进行任何有意义的操作)。

String s = (String) v.get(0);
Integer i = (Integer) v.get(1);
Hashtable h = (Hashtable) v.get(2);

选角很快就过时了。除此之外,编译器还会向您抱怨未检查的强制转换。像这样进行强制转换的最紧迫的问题是,Vector 的使用者必须知道它在 编译时处的值的类,以便正确地进行强制转换。在 Vector 的生产者和消费者彼此完全隔离的情况下(想想 RPC 消息) ,这可能是一个致命的问题。

输入泛型。泛型尝试创建强类型类来执行泛型操作。

ArrayList<String> aList = new ArrayList<String>();
aList.add("One");
String element = aList.get(0); // no cast needed
System.out.println("Got one: " + element);

设计模式书鼓励读者从合同的角度思考,而不是具体的类型。将变量从它们的实现类中分离出来是明智的(以及代码重用)。

考虑到这一点,您可能认为所有实现 List 对象都应该执行相同的操作: add()get()size()等等。通过一点反射,您可以想象许多 List 操作的实现以不同的方式遵守 List 契约(例如 ArrayList)。然而,这些对象处理的数据类型与对它们执行的操作是正交的。

把它们放在一起,你会经常看到以下类型的代码:

List<String> L = new ArrayList<String>();

您应该将其理解为“ L 是一种用于处理 String 对象的 List”。当您开始处理 Factory 类时,关键是要处理契约而不是特定的实现。工厂在运行时生产各种类型的对象。

使用泛型相当容易(大多数时候)。

有一天,您可能会决定实现自己的泛型类。也许您希望编写一个新的数据库抽象接口,以消除各种数据存储之间的差异。定义该泛型类时,将使用 <t>作为将由方法操作的对象类型的占位符。

如果您仍然感到困惑,请使用 List 的泛型类,直到您感到满意为止。稍后,您可以更加自信地深入实现。或者您可以查看 JRE 附带的各种 List 类的源代码。开放源代码是很好的方式。

看一下 Oracle/Sun 关于仿制药的文献。 干杯。

为了补充这里已经很好的答案:

方法论点:

List<? extends Foo>

如果您不打算更改列表,只是希望列表中的所有内容都可以分配给“ Foo”类型,那么这是一个很好的选择。这样,调用方就可以传入 List < FooSubclass > ,您的方法就可以工作了。通常是最好的选择。

List<Foo>

如果您打算将 Foo 对象添加到方法中的列表中,那么这是一个很好的选择。调用方可能不会传递 List < FooSubclass > ,因为您打算将 Foo 添加到 List 中。

List<? super Foo>

如果你打算将 Foo 对象添加到列表中,那么这是一个很好的选择,列表中的其他内容并不重要(例如,你可以得到一个 List < Object > ,其中包含一个与 Foo 无关的“ Dog”)。

方法返回值

就像方法参数一样,但好处是相反的。

List<? extends Foo>

保证返回的 List 中的所有内容都具有“ Foo”类型。不过它可能是 List < FooSubclass > 。调用方无法添加到列表中。这是你的首选,也是目前为止最常见的情况。

List<Foo>

就像 List < ? 扩展 Foo > ,但是也允许调用者添加到 List 中。

List<? super Foo>

允许调用者将 Foo 对象添加到 List 中,但不保证从 List.get (0)返回什么... ... 它可以是从 Foo 到 Object 的任何对象。唯一可以保证的是,这不会是一个“ Dog”列表或者其他可以阻止 list.add (foo)合法化的选项。非常罕见的用例。

希望这能帮上忙,祝你好运!

总结一下... 两个问题..。

是否需要添加到列表中? 是否关心列表中的内容?

是是-使用列表 < Foo > 。

是否使用列表 < ? super Foo > 。

没有是-使用 < ? 扩展 Foo > ——-最常见。

没有用。

List, List<?>, and List<? extends Object>是一回事。第二点更加明确。对于这种类型的列表,您无法知道哪些类型可以合法地放入其中,而且您也不知道可以从中得到什么类型,除了它们将是对象之外。

List<Object>特别表示列表包含任何类型的对象。

假设我们列一张 Foo的清单:

List<Foo> foos= new ArrayList<Foo>();

Bar放入 fos 是不合法的。

foos.add(new Bar()); // NOT OK!

把任何东西放到 List<Object>中总是合法的。

List<Object> objs = new ArrayList<Object>();
objs.add(new Foo());
objs.add(new Bar());

但是你不能把一个 Bar放到一个 List<Foo>中——这才是关键。也就是说:

List<Object> objs = foos; // NOT OK!

是不合法的。

不过,可以说 fos 是某种东西的列表,但我们不知道具体是什么:

List<?> dontKnows = foos;

但那就意味着必须禁止它去

dontKnows.add(new Foo()); // NOT OK
dontKnows.add(new Bar()); // NOT OK

因为变量 dontKnows 不知道什么类型是合法的。

List < Object > 用于传递 Object 的输入类型参数。而列表 < ? > 表示通配符类型。通配符 < ? > 是未知参数类型。通配符不能用作泛型方法的类型参数,也不能用于创建类的泛型实例。通配符可用于扩展子类 List < ?扩展号码 >。放宽 Object 类型的限制,在本例中放宽“ Number”Object 类型。

我会尽量详细地回答这个问题。在泛型之前,我们只有 List(一个原始列表) ,它可以容纳几乎所有我们能想到的东西。

List rawList = new ArrayList();
rawList.add("String Item");
rawList.add(new Car("VW"));
rawList.add(new Runnable() {
@Override
public void run() {
// do some work.
}
});

原始列表的主要问题是,当我们想从这样的列表中获取任何元素时,它只能保证它是 Object,因此我们需要使用强制转换:

   Object item = rawList.get(0); // we get object without casting.
String sameItem = (String) rawList.get(0); // we can use casting which may fail at runtime.

所以结论是 List可以存储 Object (Java 中几乎所有内容都是 Object)并且总是返回 Object。

非专利药

现在让我们来谈谈泛型:

List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
stringsList.add(new Car("Fiat")); //error
String stringItem = stringsList.get(0);

在上面的例子中,我们不能在 stringsList中插入除 String以外的任何内容,因为 Java 编译器对泛型代码应用强类型检查,并且如果代码违反了类型安全,就会发出错误。当我们尝试在其中插入一个 Car实例时,我们会得到错误。它也消除了强制转换,因为你可以检查当我们 invoke得到方法。检查这个链接了解 为什么我们应该使用泛型

List<Object>

如果你读了类型擦除,那么你就会明白,List<String>, List<Long>, List<Animal>等将有不同的静态类型在编译时,但将有相同的动态类型 List在运行时。

如果我们有 List<Object>,那么它只能存储 Object在它和几乎一切是 Object在 Java。所以我们可以:

 List<Object> objectList = new ArrayList<Object>();
objectList.add("String Item");
objectList.add(new Car("VW"));
objectList.add(new Runnable() {
@Override
public void run() {


}
});
Object item = objectList.get(0); // we get object without casting as list contains Object
String sameItem = (String) objectList.get(0); // we can use casting which may fail at runtime.

看起来 List<Object>List是相同的,但实际上它们不是。 考虑以下情况:

List<String> tempStringList = new ArrayList<>();
rawList = tempStringList; // Ok as we can assign any list to raw list.
objectList = tempStringList; // error as List<String> is not subtype of List<Obejct> becuase generics are not convariant.

你可以看到我们可以把任何列表分配给原始列表,主要原因是允许向下兼容。同时,由于类型擦除,List<String>将在运行时转换为 List,而赋值无论如何都是可以的。

但是 List<Object>意味着它只能引用一个对象列表,也只能存储对象。即使 StringObject的子类型,我们也不能将 List<String>赋给 List<Object>,因为泛型不像数组那样是协变的。它们是不变的。也检查这个 链接为更多。还要检查 有个问题ListList<Object>之间的差异。

List<?>

现在我们剩下 List<?>,它基本上意味着未知类型的列表,可以引用任何列表。

List<?> crazyList = new ArrayList<String>();
List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
crazyList = stringsList; // fine

字符 ?称为通配符,而 List<?>是一个无界通配符列表。现在还有一些要注意的地方。

我们不能实例化这个列表,因为下面的代码无法编译:

List<?> crazyList = new ArrayList<?>(); // any list.

我们可以说通配符参数化类型更像接口类型,因为我们可以使用它来引用兼容类型的对象,但不能用于它自己。

List<?> crazyList2 = new ArrayList<String>();

我们不能向它插入任何项目,因为我们不知道实际的类型是什么。

crazyList2.add("Apple"); // error as you dont actually know what is that type.

现在出现了一个问题,我什么时候需要使用 List<?>

您可以将其视为一个只读列表,其中不关心项的类型。您可以使用它来调用诸如返回列表长度、打印列表等方法。

 public static void print(List<?> list){
System.out.println(list);
}

您还可以检查 List, List<?>, List<T>, List<E>, and List<Object> 给你之间的差异。

就 List 功能的差异而言,其他的回答已经回答了这个问题。就 Java 泛型的适用规则而言,这是一个复杂的主题。我写了一篇关于 Java 泛型规则的深入文章,这是链接: https://medium.com/@royalilin/java-generics-rules-1d05de86e9cb