List
,List<?>
,List<T>
,List<E>
和List<Object>
之间有何区别?
现在我不会盲目地问这个问题,所以请不要关闭这个帖子。我先介绍一下基本代码:
private static List<String> names = new ArrayList<String>();
static {
names.add("Tom");
names.add("Peter");
names.add("Michael");
names.add("Johnson");
names.add("Vlissides");
}
public static void test(List<String> set){
System.out.println(set);
}
public static void main(String args[]){
test(names);
}
我明白:
1. List
:是原始类型,因此不是typesafe
。它只会在强制转换时生成运行时错误。当演员表不好时我们想要编译时错误。不建议使用。
2. List<?>
:是一个无界的通配符。但不确定这是为了什么?因此,如果我将test
方法更改为
public static void test(List<?> set){
System.out.println(set);
}
它仍然有效。 如果您能解释一下这种用法,我将不胜感激。
编辑:如果我这样做:
public static void test(List<?> set){
set.add(new Long(2)); //--> Error
set.add("2"); //--> Error
System.out.println(set);
}
但如果我将test
更改为:
public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2"); //--> Work
System.out.println(set);
}
3. List<T>
:
public static void test(List<T> set){ //T cannot be resolved
System.out.println(set);
}
我想我不明白这种语法。我看到这样的东西,它的确有效:
public <T> T[] toArray(T[] a){
return a;
}
请为我解释一下吗?有时,我会看到<T>
,<E>
或<U>
,<T,E>
。它们是一样的还是代表不同的东西?
4。List<Object>
public static void test(List<Object> set){
System.out.println(set);
}
然后我收到了以下代码的错误The method test(List<Object>) is not application for the argument List<String>
。我很迷惑。我认为String
是Object
的一部分?
public static void main(String args[]){
test(names);
}
编辑:如果我试试这个
test((List<Object>)names);
然后我得到Cannot cast from List<String> to List<Object>
答案 0 :(得分:72)
1)正确
2)您可以将其视为“只读”列表,您不关心项目的类型。例如,由返回列表长度的方法使用。
3)T,E和U是相同的,但人们倾向于使用例如T表示类型,E表示Element,V表示值,K表示键。编译的方法表示它采用了某种类型的数组,并返回一个相同类型的数组。4)你不能混合橙子和苹果。如果可以将字符串列表传递给期望对象列表的方法,则可以将String添加到String列表中。 (并非所有对象都是字符串)
答案 1 :(得分:25)
最后一部分: 虽然String是Object的子集,但List&lt; String&gt;不是从List&lt; Object&gt;继承。
答案 2 :(得分:20)
符号List<?>
表示“某个列表(但我不是说什么)”。由于test
中的代码适用于列表中的任何类型的对象,因此它可用作正式的方法参数。
使用类型参数(如第3点所述),需要声明type参数。 Java语法是将<T>
放在函数前面。这与在使用方法体中的名称之前将形式参数名称声明为方法完全类似。
关于List<Object>
不接受List<String>
,这是有道理的,因为String
不是Object
;它是Object
的子类。修复是声明public static void test(List<? extends Object> set) ...
。但是extends Object
是多余的,因为每个类都直接或间接地扩展Object
。
答案 3 :(得分:13)
您无法将List<String>
强制转换为List<Object>
的原因是它会允许您违反List<String>
的约束。
考虑以下场景:如果我有List<String>
,则应该只包含String
类型的对象。 (这是一个final
类)
如果我可以将其投放到List<Object>
,那么我就可以将Object
添加到该列表中,从而违反List<String>
的原始合同。
因此,一般来说,如果班级C
继承自班级P
,则不能说GenericType<C>
也会继承自GenericType<P>
。
N.B。我已经在之前的回答中对此进行了评论,但希望扩展它。
答案 4 :(得分:5)
在第三点,“T”无法解析,因为它未声明,通常在声明泛型类时,您可以使用“T”作为bound type parameter的名称,许多在线示例包括 oracle's tutorials 使用“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> {}
在你的第四个ponint中虽然有效地Sting是Object的子类型,但在泛型类中没有这样的关系,List<String>
不是List<Object>
的子类型它们是编译器的两种不同类型从观点来看,最好在this blog entry
答案 5 :(得分:5)
我建议阅读Java益智游戏。它很好地解释了声明中的继承,泛型,抽象和通配符。 http://www.javapuzzlers.com/
答案 6 :(得分:3)
让我们在Java历史的背景下讨论它们;
List
:列表表示它可以包含任何对象。 List在Java 5.0之前发布; Java 5.0引入了List,以实现向后兼容。
List list=new ArrayList();
list.add(anyObject);
List<?>
: ?
表示未知对象而不是任何对象;通配符?
介绍用于解决Generic Type构建的问题;见wildcards;
但这也会导致另一个问题:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
List< T> List< E>
在项目Lib中没有T或E类型的前提下表示通用声明。
List< Object>
表示通用参数化。答案 7 :(得分:2)
问题2没问题,因为“System.out.println(set);”表示“System.out.println(set.toString());” set是List的一个实例,因此complier将调用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:集合不允许类型参数协方差。但阵列确实允许协方差。
答案 8 :(得分:2)
<强>理论强>
String[]
可以投放到Object[]
但
List<String>
无法投放到List<Object>
。
<强>实践强>
对于列表,它比它更精细,因为在编译时,不会检查传递给方法的List参数的类型。方法定义也可以说List<?>
- 从编译器的角度来看它是等价的。这就是OP的示例#2给出运行时错误而不是编译错误的原因。
如果你小心处理传递给方法的List<Object>
参数,所以你不强制对列表的任何元素进行类型检查,那么你可以使用List<Object>
来定义你的方法,但事实上从调用代码中接受List<String>
参数。
A。因此,此代码不会产生编译或运行时错误,并且实际上(并且可能会令人惊讶地?)工作:
public static void test(List<Object> set) {
List<Object> params = new List<>(); // This is a List<Object>
params.addAll(set); // A String can be added to List<Object>
params.add(new Long(2)); // A Long can be added to List<Object>
System.out.println(params);
}
public static void main(String[] args) {
List<String> argsList = Arrays.asList(args);
test(argsList); // The object passed is a List<String>
}
B. 此代码会产生运行时错误:
public static void test(List<Object> set) {
List<Object> params = set; // Surprise! Runtime error
set.add(new Long(2)); // Also a runtime error
System.out.println(set);
}
public static void main(String[] args) {
List<String> argsList = Arrays.asList(args);
test(argsList);
}
C。此代码会产生运行时错误(java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]
):
public static void test(Object[] set) {
Object[] params = set; // This is OK even at runtime
params[0] = new Long(2); // Surprise! Runtime error
System.out.println(params);
}
public static void main(String[] args) {
test(args);
}
在B中,参数set
在编译时不是类型List
:编译器将其视为List<?>
。存在运行时错误,因为在运行时,set
成为从main()
传递的实际对象,并且是List<String>
。 List<String>
无法转换为List<Object>
。
在C中,参数set
需要Object[]
。使用String[]
对象作为参数调用时,没有编译错误和运行时错误。这是因为String[]
投射到Object[]
。但test()
收到的实际对象仍为String[]
,但未发生变化。因此params
对象也变为String[]
。并且String[]
的元素0无法分配给Long
!
(希望我的一切都在这里,如果我的推理错了,我相信社区会告诉我。)
答案 9 :(得分:0)
你说得对:String是Object的一个子集。由于String比Object更“精确”,因此您应该将其转换为使用它作为System.out.println()的参数。