[英]Difference between List, List<?>, List<T>, List<E>, and List<Object>
List
、 List<?>
、 List<T>
、 List<E>
和List<Object>
之间有什么区别?
List
:是原始类型,因此不是typesafe
的。 它只会在转换错误时产生运行时错误。 当转换错误时,我们希望出现编译时错误。 不推荐使用。
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);
}
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>
。 它们都是一样的还是代表不同的东西?
这给出了错误“方法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 List<Object>
的子类?
1) 正确
2) 您可以将那个视为“只读”列表,您不关心项目的类型。例如,可以由返回列表长度的方法使用。
3) T、E 和U 是相同的,但人们倾向于使用例如T 表示类型,E 表示元素,V 表示值,K 表示键。 编译的方法说它接受了一个特定类型的数组,并返回了一个相同类型的数组。
4)你不能把橙子和苹果混在一起。 如果可以将字符串列表传递给需要 object 列表的方法,则可以将 Object 添加到字符串列表中。 (并非所有对象都是字符串)
最后一部分:虽然String是Object的子集,但是List<String>并不是继承自List<Object>。
符号List<?>
的意思是“某物的列表(但我不是在说什么)”。 由于test
中的代码适用于列表中的任何类型的 object,因此它用作正式方法参数。
使用类型参数(如第 3 点),需要声明类型参数。 Java 的语法是将<T>
放在 function 的前面。这完全类似于在方法主体中使用名称之前向方法声明形式参数名称。
关于List<Object>
不接受List<String>
,这是有道理的,因为String
不是Object
; 它是Object
的子类。 解决方法是声明public static void test(List<? extends Object> set)...
。 但是extends Object
是多余的,因为每个 class 直接或间接扩展Object
。
您不能将List<String>
转换为List<Object>
的原因是它会让您违反List<String>
的约束。
考虑以下场景:如果我有一个List<String>
,它应该只包含String
类型的对象。 (这是final
堂课)
如果我可以将其转换为List<Object>
,那么我就可以将Object
添加到该列表中,从而违反了List<String>
的原始合同。
因此,一般来说,如果 class C
继承自 class P
,则不能说GenericType<C>
也继承自GenericType<P>
。
注意我已经在之前的回答中对此发表了评论,但想对其进行扩展。
让我们在 Java 的历史背景下谈谈它们;
List
:List表示可以包含任意Object。List在Java 5.0之前的版本中; Java 5.0 引入了 List,用于向后兼容。
List list=new ArrayList();
list.add(anyObject);
List<?>
: ?
表示未知 Object 不是任何 Object; 通配符?
introduction是为了解决Generic Type构建的问题; 见通配符; 但这也导致了另一个问题:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
List< T> List< E>
表示在您的项目 Lib 中没有 T 或 E 类型的前提下的通用声明。
List< Object>
表示泛型参数化。理论
String[]
可以转换为Object[]
但
List<String>
不能转换为List<Object>
。
实践
对于列表,它比这更微妙,因为在编译时不检查传递给方法的列表参数的类型。 方法定义还不如说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()
传递的实际 object,即List<String>
。 无法将List<String>
强制转换为List<Object>
。
在 C 中,参数set
需要一个Object[]
。 以String[]
object 作为参数调用时没有编译错误和运行时错误。 那是因为String[]
转换为Object[]
。 但是test()
收到的实际 object 仍然是String[]
,它没有改变。 所以params
object 也变成了String[]
。 并且String[]
的元素 0 不能分配给Long
!
(希望我这里的一切都是正确的,如果我的推理是错误的,我相信社区会告诉我。更新:我已经更新了示例 A 中的代码,以便它可以实际编译,同时仍然显示所提出的观点。)
在你的第三点,“T”无法解析,因为它没有声明,通常当你声明一个泛型 class 时,你可以使用“T”作为绑定类型参数的名称,许多在线示例包括
oracle 的教程
都使用“T”作为类型参数的名称,例如,您声明一个 class,例如:
public class FooHandler<T>
{
public void operateOnFoo(T foo) { /*some foo handling code here*/}
}
你是说FooHandler's
operateOnFoo
方法需要一个类型为“T”的变量,它在 class 声明本身上声明,考虑到这一点,你可以稍后添加另一个方法,如
public void operateOnFoos(List<T> foos)
在所有情况下,T、E 或 U 都有类型参数的所有标识符,您甚至可以有多个使用语法的类型参数
public class MyClass<Atype,AnotherType> {}
在你的第四个观点中,虽然实际上 Sting 是 Object 的子类型,但在 generics 类中没有这种关系, List<String>
不是List<Object>
的子类型,从编译器的角度来看,它们是两种不同的类型,这最好在这篇博文中解释
我建议阅读 Java 益智游戏。 它很好地解释了 inheritance、generics、抽象和声明中的通配符。 http://www.javapuzzlers.com/
问题 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:集合不允许类型参数协变。 但是数组确实允许协变。
你是对的:String 是 Object 的子集。由于 String 比 Object 更“精确”,你应该转换它以将其用作 System.out.println() 的参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.