Java Generics:List,List <object>,List <! - ? - > </object>

时间:2009-01-29 00:56:34

标签: java generics

有人可以尽可能详细地解释以下类型之间的差异吗?

List
List<Object>
List<?>

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

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

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

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

12 个答案:

答案 0 :(得分:101)

正如其他帖子所指出的那样,您要问的是一个名为泛型的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);

施法变得非常快。更重要的是,编译器向你抱怨未经检查的强制转换。有关这方面的生动示例,请使用Apache的XML-RPC库(无论如何都是版本2)。最重要的问题是,Vector的消费者必须在编译时知道其值的确切类,才能正确转换。如果Vector的生产者和消费者彼此完全隔离,这可能是一个致命的问题。

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

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操作的实现可以选择遵守合同以各种方式(例如ArrayList)。但是,这些对象处理的数据类型将作为运行时考虑因素留给您(泛型类的用户)。把它们放在一起,你会经常看到以下代码行:

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

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

使用泛型很容易(大多数时候)。但是,有一天你可能决定要实现泛型类。也许您已经想到了一个很棒的新List实现。定义该类时,可以使用<t>作为占位符,以获取将由方法操作的对象类型。如果您感到困惑,请使用List的通用类,直到您感到舒服为止。然后,您可以更自信地深入了解实施。或者,您可以查看JRE附带的各种List类的源代码。开源很棒。

查看Oracle / Sun docs about generics。 欢呼声。

答案 1 :(得分:40)

用我自己的简单术语:

  

列表

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

  

列表与LT;对象&gt;

将创建一个可以容纳任何类型对象的列表,但只能分配另一个 List&lt; Object&gt;

例如,这不起作用;

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

最后

  

列表与LT;?&GT;

将允许您指定任何类型,包括

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

这将被称为 unknown 的集合,因为 unknown 的共同点是Object,你将能够获取对象(巧合)

unknown 的重要性在与子类化一起使用时出现:

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 层次结构的 unknow 的集合。

答案 2 :(得分:15)

我建议您使用Sun Microsystems提供的优秀Java Generics tutorial"advanced" Generics tutorial。另一个很好的资源是Java Generics and Collections书。

答案 3 :(得分:15)

在这里添加已经很好的答案:

方法参数:

List<? extends Foo>

如果你不打算改变列表,那么

是一个很好的选择,只关心列表中的所有内容都可以分配给'Foo'。这样,调用者可以传入List&lt; FooSubclass&gt;你的方法有效。通常是最好的选择。

List<Foo>

如果您打算将Foo对象添加到方法中的列表中,那么这是一个很好的选择。调用者可能无法传入List&lt; FooSubclass&gt;,因为您打算将Foo添加到列表中。

List<? super Foo>

如果你打算将Foo对象添加到列表中,那么这是一个很好的选择,并且列表中的其他内容并不重要(也就是说,你可以获得一个List&lt; Object&gt;包含与之无关的'Dog'富)。

方法返回值

就像方法论证一样,但好处却逆转了。

List<? extends Foo>

保证返回List中的所有内容都有'Foo'类型。它可能是List&lt; FooSubclass&gt;虽然。来电者无法添加到列表中。这是您的首选,也是迄今为止最常见的案例。

List<Foo>

就像List&lt;?延伸Foo&gt;但也允许调用者添加到List。不常见。

List<? super Foo>

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

我希望有所帮助。祝你好运!

PS。总结......两个问题......

  

你需要添加到列表吗?你关心列表中的内容吗?

     

是是 - 使用List&lt; Foo&gt;。

     

是否 - 使用List&lt;?超级Foo&gt;。

     

不是 - 使用&lt;?延伸Foo&gt; ---最常见的。

     

不是否 - 使用&lt;?&gt;。

答案 4 :(得分:4)

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

List

会生成大量编译器警告,但大部分相当于:

List<Object>

虽然:

List<?>

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

List<? extends SomeOtherThing>

答案 5 :(得分:4)

我会尽力详细解答这个问题。在泛型之前,我们只有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);

在上面的例子中,我们不能在String中插入stringsList以外的任何内容,因为Java编译器将强类型检查应用于通用代码,并在代码违反类型安全时发出错误。当我们尝试在其中插入Car实例时,我们会收到错误。它也消除了强制转换,因为你可以检查我们invoke获取方法的时间。请查看此链接以了解why we should use generics

List<Object>

如果您阅读了类型擦除,那么您将了解List<String>, List<Long>, List<Animal>等在编译时将具有不同的静态类型,但在运行时将具有相同的动态类型List

如果我们有List<Object>,那么它只能存储Object,几乎所有内容都是Object。所以我们可以:

 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>,因为泛型不像数组那样协变。它们是不变的。另请查看此link以获取更多信息。另请查看此questionListList<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> here之间的区别。

答案 6 :(得分:3)

最简单的解释是:第二项是可以容纳任何类型的列表,您可以向其添加对象:

List<Object>

您列出的第一个项目被视为与此基本相同,但您将收到编译器警告,因为它是“原始类型”。

List

第三个是可以包含任何类型的列表,但是您无法向其添加任何内容:

List<?> 

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

答案 7 :(得分:1)

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

答案 8 :(得分:0)

为了补充Rob提到的教程,这里有一个解释主题的wikibook:
http://en.wikibooks.org/wiki/Java_Programming/Generics


编辑:

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

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

  3. 单独使用的通配符,因此它匹配任何内容

  4. 在这一点上得出结论几乎没有任何差异或者没有任何差别,这是天真的吗?

答案 9 :(得分:0)

  

我想何时使用

public void CanYouGiveMeAnAnswer( List l ){}

当你无法完成所有的自我投射时。

  

我想何时使用

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

如果要限制列表的类型。例如,这将是一个无效的论点。

 new ArrayList<String>();
  

我想何时使用

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

绝大多数都没有。

答案 10 :(得分:0)

List, List<?>, and List<? extends Object>是一回事。第二个是更明确的。对于这种类型的列表,您无法知道哪些类型是合法的,并且您不了解可以从中获取的类型,除了它们将是对象。

List<Object>具体表示该列表包含任何类型的对象。

假设我们列出了Foo

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

Bar放入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不知道哪些类型是合法的。

答案 11 :(得分:0)

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