List,List <?>,List <T>,List <E>和List <Object>之间的区别


194

之间有什么区别ListList<?>List<T>List<E>,和List<Object>

1.清单

List:是原始类型,因此不是typesafe。仅在强制转换失败时才会生成运行时错误。当强制转换错误时,我们需要一个编译时错误。不建议使用。

2.列表<?>

List<?>是一个无界通配符。但是我不确定它是干什么的?我可以打印一个List<?>没有问题的文件:

public static void test(List<?> list){
    System.out.println(list);   // Works
}

为什么我不能向中添加项目List<?>

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3.列表<T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

我不明白这种语法。我看到了这样的东西,它的工作原理是:

public <T> T[] toArray(T[] a){
    return a;   
}

有时候,我看<T>,或者<E>,或者<U><T,E>。它们都是相同的还是代表不同的东西?

4.列表<对象>

这将产生错误“该方法test(List<Object>)不适用于参数List<String>”:

public static void test(List<Object> list){
    System.out.println(list);
}

如果尝试此操作,则会收到“无法从投射List<String>List<Object>”的信息:

test((List<Object>) names);

我很困惑。String是的子类Object,那么为什么不是List<String>的子类List<Object>呢?

Answers:


77

1)正确

2)您可以将其视为“只读”列表,而不关心项目的类型。例如,可以由返回列表长度的方法使用。

3)T,E和U相同,但是人们倾向于使用T作为类型,E表示Element,V表示值,K表示键。编译的方法表示它接受了某种类型的数组,并返回了相同类型的数组。

4)你不能混合桔子和苹果。如果可以将字符串列表传递给需要对象列表的方法,则可以将对象添加到字符串列表中。(并非所有对象都是字符串)


2
+1代表上的只读列表2。我写了一些代码来证明这一点2。tyvm
Thang Pham

人们为什么会使用List<Object>
唐潘(Thang Pham)

3
这是一种创建可以接受任何类型项目的列表的方式,您很少使用它。
Kaj

实际上,考虑到您现在根本没有标识符,我认为不会有人使用它。
if_zero_equals_one 2011年

1
@if_zero_equals_one是,但是您会收到编译器警告(它会警告并说您正在使用原始类型),并且您永远都不想使用警告来编译代码。
Kaj

26

对于最后一部分:尽管String是Object的子集,但是List <String>并非从List <Object>继承。


11
很好的一点;许多人认为,因为类C继承自类P,所以List <C>也继承自List <P>。正如您指出的,情况并非如此。原因是,如果我们可以将List <String>转换为List <Object>,则可以将Objects放入该列表中,从而在尝试检索元素时违反了List <String>的原始约定。
彼得

2
+1。好点。那么人们为什么要使用它List<Object>呢?
唐潘(Thang Pham)

9
List <Object>可用于存储不同类的对象的列表。
2011年6

20

该符号的List<?>意思是“某物的列表(但我不是在说什么)”。由于其中的代码test适用于列表中的任何类型的对象,因此它可以用作形式方法的参数。

使用类型参数(如第3点中所述)要求声明类型参数。相应的Java语法放在<T>函数的前面。这完全类似于在方法主体中使用名称之前为方法声明形式参数名称。

关于List<Object>不接受a List<String>,这是有道理的,因为a String不是Object; 它是的子类Object。解决方法是声明public static void test(List<? extends Object> set) ...。但是这extends Object是多余的,因为每个类都直接或间接扩展Object


人们为什么会使用List<Object>
唐潘(Thang Pham)

10
我认为“某物的列表”是更好的意思,List<?>因为该列表属于某些特定但未知的类型。List<Object>因为它确实可以包含任何内容,所以它实际上是“所有内容的列表”。
ColinD 2011年

1
@ColinD-我的意思是“任何东西”。但是你是对的。它的意思是,“列表,但我不会告诉您什么”。
泰德·霍普

@ColinD是他的意思,为什么您要重复他的话?是的,它是用一些不同的词写的,但是含义是相同的……
user25

14

你可以不投之所以List<String>List<Object>的是,它可以让你违反的制约List<String>

考虑以下情形:如果我有一个List<String>,它应该只包含type的对象String。(这是一final堂课)

如果我可以将其转换为List<Object>,则允许我将其添加Object到该列表中,从而违反的原始合同List<String>

因此,在一般情况下,如果类C从类继承P,你不能说GenericType<C>也继承GenericType<P>

注意:我已经在之前的回答中对此进行了评论,但想对此进行扩展。


tyvm,我上载了您的评论和答案,因为这是一个很好的解释。现在人们会在哪里以及为什么使用List<Object>
唐潘(Thang Pham)

3
通常,您不应使用List<Object>它,因为它会破坏泛型的目的。但是,在某些情况下,旧代码可能具有List接受的不同类型,因此您可能希望对代码进行改型以使用类型参数化,以避免对原始类型的编译器警告。(但功能未更改)
Peter


5

让我们在Java历史的背景下谈论它们;

  1. List

列表意味着它可以包含任何对象。列表在Java 5.0之前的版本中;Java 5.0引入了List,以实现向后兼容。

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>

?表示未知对象而不是任何对象;通配符?介绍用于解决“通用类型”构建的问题;看到通配符 ; 但这还会导致另一个问题:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

表示在项目Lib中没有T或E类型的前提下的通用声明。

  1. List< Object> 表示通用参数化。

5

第三点,“ T”无法解析,因为未声明它,通常在声明泛型类时,可以使用“ T”作为绑定类型参数的名称,许多在线示例(包括oracle教程)都使用“ T”作为类型参数的名称,例如,您声明一个类,例如:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

您说的是该FooHandler's operateOnFoo方法需要一个在类声明本身上声明的类型为“ T”的变量,因此请稍后再添加另一个方法,例如

public void operateOnFoos(List<T> foos)

在T,E或U的所有情况下,所有类型参数的标识符,您甚至可以拥有多个使用以下语法的类型参数:

public class MyClass<Atype,AnotherType> {}

在您的第4章中,尽管Sting有效地是Object的子类型,但在泛型类中没有这种关系,List<String>不是子类型,List<Object>从编译器的角度来看,它们是两种不同的类型,这在本博客文章中得到了最好的解释。


5

理论

String[] 可以投射到 Object[]

List<String>无法转换为List<Object>

实践

对于列表,它比那要微妙得多,因为在编译时,不会检查传递给方法的List参数的类型。方法定义也可以这么说List<?>-从编译器的角度来看,它是等效的。这就是OP的示例#2给出运行时错误而不是编译错误的原因。

如果您List<Object>谨慎处理传递给方法的参数,而不用对列表的任何元素进行类型检查,则可以使用定义方法,List<Object>但实际上可以List<String>从调用代码中接受参数。

答: 因此,此代码不会产生编译或运行时错误,并且实际上(也许令人惊讶?)可以工作:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. 此代码将给出运行时错误:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. 这段代码将给出运行时错误(java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

在B中,参数在编译时set不是类型化的List:编译器将其视为List<?>。存在运行时错误,因为在运行时set会成为从传递的实际对象main(),即为List<String>List<String>无法将A 强制转换为List<Object>

在C中,参数set需要一个Object[]。以String[]对象作为参数调用时,没有编译错误,也没有运行时错误。那是因为String[]强制转换为Object[]。但是接收到的实际对象test()仍然是String[],它没有改变。因此,params对象也变为String[]。并且a的元素0 String[]不能分配给Long

(希望我在这里掌握了所有内容,如果我的推理是错误的,我敢肯定社区会告诉我。更新:我已经更新了示例A中的代码,以使它可以实际编译,同时仍然显示出要点。)


我已经尝试了您的示例A,但它不起作用List<Object> cannot be applied to List<String>。您无法传递ArrayList<String>期望的方法ArrayList<Object>
parsecer

谢谢,很晚才对示例A进行了调整,以便它现在可以正常工作。主要更改是在main()中通用地定义了argsList。
radfast

4

问题2可以,因为“ System.out.println(set);” 表示“ System.out.println(set.toString());” set是List的一个实例,因此编译器将调用List.toString();。

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

问题3:这些符号相同,但是您可以给它们指定不同的规格。例如:

public <T extends Integer,E extends String> void p(T t, E e) {}

问题4:集合不允许类型参数协方差。但是数组确实允许协方差。


0

您说对了:字符串是Object的子集。由于String比Object更“精确”,因此您应该将其强制转换为System.out.println()的参数。

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.