Javagenerics:List,List <Object>,List <?>
有人能够尽可能详细地解释下列types之间的区别吗?
List List<Object> List<?>
让我把这个更具体一些。 我什么时候想要使用
// 1 public void CanYouGiveMeAnAnswer(List l) { } // 2 public void CanYouGiveMeAnAnswer(List<Object> l) { } // 3 public void CanYouGiveMeAnAnswer(List<?> l) { }
正如其他职位已经指出,你在问一个叫做generics的Java特性。 在C ++中,这被称为模板。 爪哇的野兽远远不够应付。
让我在function上回答你的问题(如果这不是OO讨论的顽皮的话)。
在generics之前,你有像Vector这样的老混凝土类。
Vector V = new Vector();
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库(第二版)。 最重要的问题是,Vector的消费者在编译时必须知道它的值的确切类,才能正确投射。 如果Vector和消费者的生产者完全隔离,这可能是一个致命的问题。
进入generics。 generics尝试创build强types类来执行generics操作。
ArrayList<String> aList = new ArrayList<String>(); aList.add("One"); String element = aList.get(0); // no cast needed System.out.println("Got one: " + element);
现在,如果你看一下四个“ devise模式”书中臭名昭着的黑帮,你会注意到,从实施阶级中分离variables是有一定的智慧的。 考虑合同而不是实施更好。 所以,你可能会说所有的List对象都做同样的事情: add()
, get()
, size()
等等。但是List操作有许多实现可以select以各种方式服从合约(比如ArrayList
)。 但是,这些对象所处理的数据types作为运行时考虑事项留给了generics类的用户。 把它放在一起,你会非常频繁地看到下面这行代码:
List<String> L = new ArrayList<String>();
你应该把它看作“L是一种处理string对象的List”。 当你开始处理Factory类时,处理合同而不是特定的实现是至关重要的。 工厂在运行时产生各种types的对象。
使用generics非常简单(大部分时间)。 然而,你可能决定要实现一个generics类的一个糟糕的一天。 也许你已经想到了一个伟大的新的List实现。 当你定义这个类的时候,你可以使用<t>
作为方法操作的对象的占位符。 如果您感到困惑,请使用列表的通用类,直到您感觉舒适为止。 然后,您可以更加自信地投入实施。 或者您可以查看JRE附带的各种List类的源代码。 开源是伟大的方式。
看看有关generics的Oracle / Sun 文档 。 干杯。
用我自己的简单来说:
名单
会声明一个普通的集合,可以容纳任何types,并且总会返回Object。
列表<对象>
将创build一个可以容纳任何types对象的列表,但只能分配另一个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 );
最后
名单<?>
将让你分配任何types,包括
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作为types不会造成混淆,那是我想到的唯一的树。
这里的区别在于,l是属于Collection层次结构的未知 集合 。
我将向您介绍优秀的Javagenerics教程以及Sun Microsystems提供的“高级”generics教程 。 另一个很好的资源是Javagenerics和集合书。
在这里添加已经很好的答案:
方法参数:
List<? extends Foo>
如果你不打算修改这个列表,那么这个选项是不错的select,只关心列表中的所有内容都可以分配给“Foo”。 这样,调用者可以传递一个List <FooSubclass>并且你的方法可以工作。 通常是最好的select。
List<Foo>
如果您打算将Foo对象添加到您的方法列表中,这是一个不错的select。 调用者可能不会传递List <FooSubclass>,因为您打算将Foo添加到列表中。
List<? super Foo>
如果您打算将Foo对象添加到列表中,那么这是一个不错的select,而且列表中还有什么不重要(即,您可以获取包含与“Foo”无关的“Dog”的List <Object>)。
方法返回值
就像方法论点,但扭转了好处。
List<? extends Foo>
保证返回列表中的所有内容都有“Foo”types。 尽pipe它可能是List <FooSubclass>。 来电者不能添加到列表。 这是你的select,也是目前最常见的情况。
List<Foo>
就像List <? 扩展Foo>,但也允许调用者添加到列表。 不常见。
List<? super Foo>
允许调用者将Foo对象添加到列表中,但不能保证从list.get(0)返回的内容…它可以是从Foo到Object的任何内容。 唯一的保证是,这不会是一个“狗”或其他select,可以防止list.add(foo)是合法的。 非常罕见的用例。
我希望有帮助。 祝你好运!
PS。 总结…两个问题…
你需要添加到列表? 你在意列表中有什么?
是的是 – 使用列表<Foo>。
是的没有 – 使用列表<? 超级富“。
否是 – 使用<? 延伸Foo> —最常见。
不要使用<?>。
最简单的解释是不是“RTFM”:
List
会产生大量的编译器警告,但大致相当于:
List<Object>
而:
List<?>
基本上意味着它是通用的,但是你不知道通用types是什么。 它是伟大的摆脱编译器警告,当你不能修改返回types的其他东西,只是返回列表。 它的forms更有用:
List<? extends SomeOtherThing>
最简单的解释是:第二项是一个可以容纳任何types的列表,你可以添加对象:
List<Object>
您列出的第一个项目被视为基本上等同于此,除非您将得到编译器警告,因为它是“原始types”。
List
第三个是可以保存任何types的列表,但不能添加任何内容:
List<?>
基本上,当您真正拥有一个可以包含任何对象的列表并且希望能够将元素添加到列表中时,使用第二个窗体( List<Object>
)。 当您接收到列表作为方法返回值时,您将使用第三种forms( List<?>
),并且您将遍历列表,但从不向其中添加任何内容。在Java 5下的新代码编译中,不要使用第一种forms后来。
我会尽力详细回答这个问题。 在generics之前,我们只有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
,因此我们需要使用cast作为:
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。
generics
现在让我们来谈谈generics。 考虑下面的例子:
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编译器将强types检查应用到generics代码,并且如果代码违反types安全性,则会发出错误。 当我们尝试在其中插入一个Car
实例时,我们会遇到错误。 它也消除了强制转换,因为您可以检查何时invoke
get方法。 查看这个链接了解为什么我们应该使用generics 。
List<Object>
如果你阅读了关于types擦除,那么你将会明白, List<String>, List<Long>, List<Animal>
等在编译时将具有不同的静态types,但在运行时将具有相同的dynamictypesList
。
如果我们有List<Object>
那么它只能存储Object
中的Object
,几乎所有东西都是Java中的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>
将在运行时由于types擦除而被转换为List
,并且分配无论如何都是可以的。
但List<Object>
意味着它只能引用一个对象列表,也可以只存储对象。 即使String
是Object
子types,我们也不能将List<String>
赋值给List<Object>
因为generics不像数组一样是协变的。 他们是不变的。 另外检查这个链接更多。 在这个问题中还要检查List
和List<Object>
之间的区别。
List<?>
现在我们还剩下List<?>
,它基本上是指未知types的列表,可以引用任何列表。
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.
我们可以说通配符参数化types更像是一个接口types,因为我们可以用它来引用一个兼容types的对象,但不能为它自己。
List<?> crazyList2 = new ArrayList<String>();
我们不能插入任何项目,因为我们不知道types是什么。
crazyList2.add("Apple"); // error as you dont actually know what is that type.
现在问题出现什么时候我想要使用List<?>
?
您可以将其视为只读列表,您不关心这些项目的types。 你可以使用它来调用返回列表的长度,打印等方法。
public static void print(List<?> list){ System.out.println(list); }
您还可以在这里检查List, List<?>, List<T>, List<E>, and List<Object>
之间的区别。
我这样说: List
和List<Object>
可以包含任何types的对象, List<?>
包含未知types的元素,但是一旦捕获了这个types,它只能包含该types的元素。 这就是为什么它是这三个types的唯一types安全变体,因此通常更可取。
为了补充Rob提到的教程,下面是一个解释主题的wiki:
http://en.wikibooks.org/wiki/Java_Programming/Generics
编辑:
-
列表中的项目types没有限制
-
列表中的项目必须扩展Object
-
通配符本身使用,所以它匹配任何东西
在这一点上,我会得出一个天真的结论:几乎没有任何区别。
我什么时候想要使用
public void CanYouGiveMeAnAnswer( List l ){}
当你不能做所有的自我铸造。
我什么时候想要使用
public void CanYouGiveMeAnAnswer( List l<Object> ){}
当你想限制列表的types。 例如,这将是一个无效的论点。
new ArrayList<String>();
我什么时候想要使用
public void CanYouGiveMeAnAnswer( List l<?> ){}
绝大多数都不是。
List, List<?>, and List<? extends Object>
List, List<?>, and List<? extends Object>
是一回事。 第二个更明确。 对于这种types的列表,你不知道什么types是合法的,除了它们将是对象之外,你不知道什么types可以从中获得。
List<Object>
具体表示该列表包含任何types的对象。
假设我们列出Foo
:
List<Foo> foos= new ArrayList<Foo>();
把Bar
放进Bar
是不合法的。
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
因为variablesdontKnows不知道什么types是合法的。
List <Object>是为了传递一个Object的inputtypes参数。 虽然列表<? >表示通配符types。 通配符<? >是未知的参数types。 通配符不能用作generics方法的types参数,不能用于创build类的generics实例。 通配符可以用来扩展一个子类,List <? 扩展Number>。 放宽对象types的限制,在这种情况下放松“数量”对象types。