List、List<?>、List<T>、List<E>和List<Object>

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

1. 列表

List:是原始类型,因此不是typesafe。它只会在强制转换错误时生成运行时错误。当强制转换错误时,我们希望出现编译时错误。不建议使用。

2. List< ?比;

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.List< 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. List< Object>

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

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

如果我尝试这样做,那么我得到“不能从List<String>转换到List<Object>”:

test((List<Object>) names);

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

173177 次浏览

你是对的:String是Object的子集。由于String比Object更“精确”,您应该强制转换它以将其用作System.out.println()的参数。

1)正确

2)你可以认为这是一个“只读”列表,在那里你不关心项目的类型。可以被返回列表长度的方法使用。

3) T、E和U是相同的,但人们倾向于使用例如T表示类型,E表示元素,V表示值,K表示键。编译的方法说它获取了一个特定类型的数组,并返回了一个相同类型的数组。

你不能把橘子和苹果混在一起。如果可以将字符串列表传递给需要对象列表的方法,则可以将Object添加到String列表中。(并不是所有对象都是字符串)

符号List<?>表示“某物的列表(但我没有说什么)”。由于test中的代码适用于列表中的任何类型的对象,因此这是一个正式的方法参数。

使用类型参数(如要点3)需要声明类型参数。Java语法是将<T>放在函数前面。这完全类似于在使用方法体中的名称之前向方法声明形式参数名称。

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

虽然String是Object的子集,但是List<String>并不是继承自List<Object>.

.

.

在你的第三点中,“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> {}

在你的第四点中,虽然实际上Sting是Object的子类型,但在泛型类中没有这样的关系,List<String>不是List<Object>的子类型,从编译器的角度来看,它们是两种不同的类型,这在这个博客条目中得到了最好的解释

不能将List<String>转换为List<Object>的原因是,这将允许您违反List<String>的约束。

考虑下面的场景:如果我有一个List<String>,它应该只包含String类型的对象。(这是一个final类)

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

因此,一般来说,如果类C继承自类P,你不能说GenericType<C>也继承自GenericType<P>

注意:我在之前的回答中已经评论过这一点,但我想在此展开。

我建议阅读Java谜题。它很好地解释了声明中的继承、泛型、抽象和通配符。 http://www.javapuzzlers.com/ < / p >

问题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:集合不允许类型参数协方差。但是数组允许协方差。

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

  1. List:

List意味着它可以包含任何对象。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>

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

  1. List< Object>表示泛型参数化。

理论

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
}

这段代码将给出一个运行时错误(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>不能强制转换为List<Object>

在C语言中,参数set需要Object[]。当它以String[]对象作为参数调用时,没有编译错误和运行时错误。这是因为String[]强制转换为Object[]。但是test()接收到的实际对象仍然是String[],它没有改变。因此,params对象也变成了String[]。并且String[]的元素0不能被赋值给Object[]0!

(希望我的一切都是正确的,如果我的推理是错误的,我相信社区会告诉我。UPDATED:我已经更新了示例A中的代码,以便它实际编译,同时仍然显示所做的重点。)