Java泛型:列表,列表<对象>,列表<?>


71

有人可以详细解释以下类型之间的区别吗?

List
List<Object>
List<?>

让我更具体一点。我什么时候要使用

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

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

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

很抱歉,您的样本应该采用List <Object> l和List <?> l的形式。在我意识到那是他的所作所为之前,我差点否决了一个复制并粘贴您所拥有内容的答案。
laz

18
嗨,伙计们,首先祝贺您有一个很好的问题。其次,您还没有接受任何答案,是在寻找更多答案还是对答案不满意?
Logicalj

1
@Logicalj我猜他不打算回来。他最后一次出现在2013年。可惜所有名声都浪费了……
Ungeheuer

Answers:


103

正如其他文章所指出的那样,您正在询问有关称为泛型的Java功能。在C ++中,这称为模板。Java中的此功能通常比C ++中的功能更易于使用。

让我从功能上回答您的问题(如果这不是面向对象的讨论的顽皮话)。

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

Vector V = new Vector();

向量可容纳您提供的任何对象。

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

他们通过将赋予它的所有值转换为对象(所有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()等有了一点点反思,你可以想像列表操作的许多实现服从以各种方式列表合同(例如ArrayList) 。但是,这些对象处理的数据类型与对其执行的操作正交。

放在一起,您将经常看到以下类型的代码:

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

您应该读为“ L是一种处理String对象的列表”。当您开始处理Factory类时,处理合同而不是特定的实现至关重要。工厂在运行时会产生各种类型的对象。

使用泛型非常容易(大多数时候)。

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

如果您仍然感到困惑,请对List使用通用类,直到您感到舒适为止。稍后,您可以更有信心地深入实施。或者,您可以查看JRE附带的各种List类的源代码。开源很棒。

看看有关泛型的Oracle / Sun文档。干杯。


8
只需登录即可+1这个绝佳答案-简洁明了!
克里斯(Chris)

不需要铸造为字符串,System.out.println(aList.get(0))这是我认为更好的方法String element = aList.get(0);
KeremBaydoğan2013年

简洁,精确和完美。非常感谢。
克里希纳

有关通用用法的最佳解释,请访问:angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
B先生

5
不讨论使用<?>
罗伊真爱

41

用我自己的简单话来说:

清单

将声明一个普通的集合,可以容纳任何类型,并且将始终返回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>();

这被称为收集未知以来的共同点未知的对象,你就能获取对象(巧合)

未知的重要性在于将其与子类一起使用时:

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是属于Collection层次结构的未知集合


@汤姆:我同意,但是我想不出比这更熟悉的层次!:)随意编辑。
OscarRyz

15

我为您提供了出色的Java Generics教程“高级” Generics教程,它们都可以从Sun Microsystems获得。另一个很棒的资源是Java Generics and Collections一书。


7
有时人们希望听到它以不同的方式解释。
菲利普斯

7
好的,但这是我的问题,我只是想一个快速而肮脏的答案,不要对您的链接那么敏感。
ForYourOwnGood,2009年

2
最终,尽管您可能已经提出了问题,但该站点的目标是使该问题成为将来访问者的更通用的参考资源。我发布了这两个教程的链接,因为它们全面解释了一个相当复杂的主题。
罗布

8
我认为社区将决定什么是回答这个问题的最佳方法。
pek

4
所有人注意:由于ForYourOwnGood和Rob之间存在分歧,Rob投票否决了ForYourOwnGood答案中的7个,因此,如果您在任何问题上都不赞成Rob,则准备丢掉一些积分。
ForYourOwnGood,2009年

15

要在此处添加已经很好的答案:

方法参数:

List<? extends Foo>

如果您不想更改列表,那是个不错的选择,只关心列表中的所有内容都可以分配为“ Foo”类型。这样,调用者可以传递List <FooSubclass>,并且您的方法有效。通常是最佳选择。

List<Foo>

如果打算将Foo对象添加到方法中的列表中,则是不错的选择。由于您打算将Foo添加到列表中,因此调用者可能不会传递List <FooSubclass>。

List<? super Foo>

如果您打算将Foo对象添加到列表中,则是个不错的选择,列表中的其他内容并不重要(即,您可以获取一个List <Object>,其中包含与Foo无关的“ Dog”)。

方法返回值

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

List<? extends Foo>

保证返回列表中的所有内容都为“ Foo”类型。它可能是List <FooSubclass>。呼叫者无法添加到列表。这是您的首选,也是迄今为止最常见的情况。

List<Foo>

就像List <?扩展了Foo>,但也允许调用方将其添加到列表中。不常见。

List<? super Foo>

允许调用者将Foo对象添加到列表中,但不能保证从list.get(0)返回什么……从Foo到Object都可以。唯一的保证是,它不会是“狗”的列表或其他可能导致list.add(foo)合法的选择。非常罕见的用例。

希望对您有所帮助。祝好运!

ps。总结两个问题

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

是是-使用List <Foo>。

是否-使用List <?超级富>。

否是-使用<?扩展Foo> ---最常见。

否否-使用<?>。


5

我将尝试详细回答。在泛型之前,我们只有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,因此出于这个原因,我们需要使用casting:

   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);

在上述情况下StringstringsList除了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>意味着它只能引用对象列表,也只能存储对象。即使String是子类型Object,我们不能分配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> 此处检查两者之间的区别。


4

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

List

会生成很多编译器警告,但主要等同于:

List<Object>

而:

List<?>

基本上意味着它是通用的,但是您不知道通用的类型是什么。当您无法修改刚刚返回List的其他事物的返回类型时,它非常适合消除编译器警告。其形式更为有用:

List<? extends SomeOtherThing>

“但大多数情况下等同于:”取决于您算作“大部分”
user102008 2013年

3

可能的最短解释是:第二项是可以保存任何类型的列表,您可以向其中添加对象:

List<Object>

列出的第一项基本上被视为与此等效,除了您会收到编译器警告,因为它是“原始类型”。

List

第三个是可以容纳任何类型的列表,但是您不能向其中添加任何内容:

List<?> 

基本上,List<Object>当您真正拥有一个可以包含任何对象的列表并且希望能够向列表中添加元素时,可以使用第二种形式()。List<?>当您收到列表作为方法的返回值时,您将使用第三种形式(),并且将在该列表上进行迭代,但是绝不对其添加任何内容切勿List在Java 5或更高版本下的新代码编译中使用第一种形式()。


2
如果您要对此表示反对,请至少礼貌地解释您认为令人反感的内容。
Eddie

“您列出的第一项基本上被视为与此等效,除了您会收到编译器警告,因为它是“原始类型”。“ 还有其他重要区别,例如您可以将分配给List<String>List但不能分配给List<Object>
user102008

@ user102008,是的,由于自变量和协方差,可以分配给生成的列表的列表有所不同。我不认为这一点在OP的问题范围之内,但这是有道理的。
Eddie

1

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


0

为了补充Rob提到的教程,这是一本解释该主题的Wikibook:http :
//en.wikibooks.org/wiki/Java_Programming/Generics


编辑:

  1. 列表中的项目类型无限制

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

  3. 本身使用通配符,因此可以匹配任何内容

我现在得出结论几乎没有/根本没有区别会天真吗?


0

我什么时候要使用

public void CanYouGiveMeAnAnswer( List l ){}

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

我什么时候要使用

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

当您想限制列表的类型时。例如,这将是无效的参数。

 new ArrayList<String>();

我什么时候要使用

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

绝不会。


0

List, List<?>, and List<? extends Object>是同一回事。第二个更为明确。对于这种类型的列表,您不知道可以放入哪些类型的合法类型,并且您对可以脱离它的类型一无所知,只是它们将成为对象。

List<Object> 特别是意味着该列表包含任何种类的对象。

假设我们列出了Foo

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

将aBar放入foos是不合法的。

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!

是不合法的。

可以肯定地说foos是某物的列表,但我们具体不知道它是什么:

List<?> dontKnows = foos;

但这意味着必须禁止它

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

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


1
ListList<?>List<? extends Object>是同一回事。” 差远了。您可以添加任何东西的List,但你不能只是添加任何东西nullList<?>
user102008

0

List <Object>用于传递Object的输入类型参数。而列表<?>代表通配符类型。通配符<?>是未知参数类型。通配符不能用作泛型方法的类型参数,也不能用于创建类的泛型实例。通配符可用于扩展子类型类List <?扩展Number>。放宽对象类型的限制,在这种情况下放宽“数字”对象类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.