[英]Java Generics: 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) { }
正如其他文章所指出的那样,您正在询问有关称为泛型的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()
等。稍加思考,您可以想象到很多List操作的实现List合约以各种方式(例如ArrayList
)。 但是,这些对象处理的数据类型与对其执行的操作正交。
放在一起,您将经常看到以下类型的代码:
List<String> L = new ArrayList<String>();
您应该读为“ L是一种处理String对象的列表”。 当您开始处理Factory类时,处理合同而不是特定的实现至关重要。 工厂在运行时会产生各种类型的对象。
使用泛型非常容易(大部分时间)。
有一天,您可能决定要实现自己的通用类。 也许您想编写一个新的数据库抽象接口,以消除各种数据存储之间的差异。 定义该通用类时,将使用<t>
作为将由方法操作的对象的占位符。
如果仍然感到困惑,请对List使用通用类,直到您感到舒适为止。 以后,您可以更有信心地深入实施。 或者,您可以查看JRE附带的各种List类的源代码。 开源很棒。
用我自己的简单话来说:
清单
将声明一个普通的集合,可以容纳任何类型,并且将始终返回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层次结构的未知 集合 。
我为您提供了出色的Java泛型教程和“高级”泛型教程 ,它们均可从Sun Microsystems获得。 另一个很棒的资源是Java Generics and Collections一书。
要在此处添加已经很好的答案:
方法参数:
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> ---最常见。
否否-使用<?>。
我将尝试详细回答。 在泛型之前,我们只有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编译器对通用代码进行了stringsList
类型检查,如果代码违反了类型安全性,则会发出错误。 当我们尝试在其中插入Car
实例时,我们会收到错误消息。 另外,它消除了强制类型转换,因为您可以在invoke
get方法时检查。 检查此链接以了解为什么我们应该使用泛型 。
List<Object>
如果您了解类型擦除,那么您将理解List<String>, List<Long>, List<Animal>
等在编译时将具有不同的静态类型,但在运行时将具有相同的动态类型List
。
如果我们有List<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>
将在运行时转换为List
,并且无论如何分配都可以。
但是List<Object>
意味着它只能引用对象列表,并且也只能存储对象。 即使String
是Object
子类型,我们也无法将List<String>
分配给List<Object>
因为泛型不像数组那样协变。 它们是不变的。 另请检查此链接以获取更多信息。 还要检查此问题中 List
和List<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>
之间的差异。
最简单的解释不是“ RTFM”:
List
会生成很多编译器警告,但主要等同于:
List<Object>
而:
List<?>
基本上意味着它是通用的,但是您不知道通用的类型是什么。 当您不能修改刚刚返回List的其他事物的返回类型时,它非常适合消除编译器警告。 其形式更为有用:
List<? extends SomeOtherThing>
可能的最简短的解释是:第二项是可以保存任何类型的列表,您可以向其中添加对象:
List<Object>
列出的第一项基本上被视为与此等效,除了您会收到编译器警告,因为它是“原始类型”。
List
第三个是可以容纳任何类型的列表,但是您不能对其添加任何内容:
List<?>
基本上,当您真正拥有一个可以包含任何对象的列表并且希望能够向列表中添加元素时,可以使用第二种形式( List<Object>
)。 当您收到列表作为方法的返回值时,您将使用第三种形式( List<?>
),并且您将遍历该列表但从不对其添加任何内容切勿在Java 5或更高版本的新代码编译中使用第一种形式( List
)。后来。
我这样说: List
和List<Object>
可以包含任何类型的对象,而List<?>
包含未知类型的元素,但是一旦捕获了该类型,它就只能包含该类型的元素。 这就是为什么它是这三个中唯一的类型安全变体,因此通常更可取。
List, List<?>, and List<? extends Object>
List, List<?>, and List<? extends Object>
是同一件事。 第二个更为明确。 对于这种类型的列表,您不知道可以放入哪种类型的合法类型,并且对这些类型可以使用的对象一无所知。
List<Object>
专门意味着列表包含任何种类的对象。
假设我们列出了Foo
:
List<Foo> foos= new ArrayList<Foo>();
禁止在foos中放置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
因为变量dontKnows不知道合法的类型。
List <Object>用于传递Object的输入类型参数。 而列表<? >代表通配符类型。 通配符<? >是未知参数类型。 通配符不能用作泛型方法的类型参数,也不能用于创建类的泛型实例。 通配符可用于扩展子类型类List <? 扩展Number>。 放宽对象类型的限制,在这种情况下放宽“数字”对象类型。
为了补充Rob提到的教程,这是一本解释该主题的Wikibook:
http://en.wikibooks.org/wiki/Java_Programming/Generics
编辑:
列表中的项目类型无限制
列表中的项目必须扩展Object
本身使用通配符,因此可以匹配任何内容
我现在得出结论几乎没有/根本没有区别会天真吗?
我什么时候要使用
public void CanYouGiveMeAnAnswer( List l ){}
当你不能做所有的自我铸造时。
我什么时候要使用
public void CanYouGiveMeAnAnswer( List l<Object> ){}
当您想限制列表的类型时。 例如,这将是无效的参数。
new ArrayList<String>();
我什么时候要使用
public void CanYouGiveMeAnAnswer( List l<?> ){}
绝不会。
在 List 功能差异方面,其他回答已经回答了这个问题。 就Java泛型的适用规则而言,这是一个复杂的话题。 我写了一篇关于Java泛型规则的深入文章,这是链接: https : //medium.com/@royalilin/java-generics-rules-1d05de86e9cb
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.