有人可以详细解释以下类型之间的区别吗?
List
List<Object>
List<?>
让我更具体一点。我什么时候要使用
// 1
public void CanYouGiveMeAnAnswer(List l) { }
// 2
public void CanYouGiveMeAnAnswer(List<Object> l) { }
// 3
public void CanYouGiveMeAnAnswer(List<?> l) { }
有人可以详细解释以下类型之间的区别吗?
List
List<Object>
List<?>
让我更具体一点。我什么时候要使用
// 1
public void CanYouGiveMeAnAnswer(List l) { }
// 2
public void CanYouGiveMeAnAnswer(List<Object> l) { }
// 3
public void CanYouGiveMeAnAnswer(List<?> l) { }
Answers:
正如其他文章所指出的那样,您正在询问有关称为泛型的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类的源代码。开源很棒。
System.out.println(aList.get(0))
这是我认为更好的方法String element = aList.get(0);
用我自己的简单话来说:
清单
将声明一个普通的集合,可以容纳任何类型,并且将始终返回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 Generics教程和“高级” Generics教程,它们都可以从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
,因此出于这个原因,我们需要使用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);
在上述情况下String
,stringsList
除了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>
泛型不是协变的像阵列。它们是不变的。另请查看此链接以获取更多信息。还要检查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<?>
当您收到列表作为方法的返回值时,您将使用第三种形式(),并且将在该列表上进行迭代,但是绝不对其添加任何内容切勿List
在Java 5或更高版本下的新代码编译中使用第一种形式()。
List<String>
,List
但不能分配给List<Object>
。
我这样说:尽管List
andList<Object>
可以包含任何类型的对象,但是List<?>
包含未知类型的元素,但是一旦捕获了该类型,它就只能包含该类型的元素。这就是为什么它是这三个类型中唯一的类型安全变体,因此通常更可取。
为了补充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, 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不知道合法的类型。
List
,List<?>
和List<? extends Object>
是同一回事。” 差远了。您可以添加任何东西的List
,但你不能只是添加任何东西null
的List<?>