public void wahey(List<Object> list) {}
wahey(new LinkedList<Number>());
对方法的调用不会进行类型检查。我甚至无法按如下方式投射参数:
wahey((List<Object>) new LinkedList<Number>());
从我的研究中,我发现不允许这样做的原因是类型安全。如果我们被允许执行上述操作,那么我们可以拥有以下内容:
List<Double> ld;
wahey(ld);
在方法wahey中,我们可以在输入列表中添加一些字符串(因为参数保持List<Object>
引用)。现在,在方法调用之后,ld引用类型为List<Double>
的列表,但实际列表包含一些String对象!
这似乎与没有泛型的Java正常工作方式不同。例如:
Object o;
Double d;
String s;
o = s;
d = (Double) o;
我们在这里做的基本上是相同的,除了这将通过编译时检查,并且只在运行时失败。带有列表的版本将无法编译。
这让我相信这纯粹是关于泛型类型限制的设计决策。我希望对这个决定有所评论?
答案 0 :(得分:9)
你在“没有泛型”的例子中所做的是一个演员表,这清楚表明你正在做一些类型不安全的事情。与泛型相当的是:
Object o;
List<Double> d;
String s;
o = s;
d.add((Double) o);
其行为方式相同(编译,但在运行时失败)。不允许您询问的行为的原因是因为它允许隐式类型不安全的操作,这在代码中更难注意到。例如:
public void Foo(List<Object> list, Object obj) {
list.add(obj);
}
这看起来非常精细且类型安全,直到你这样称呼它:
List<Double> list_d;
String s;
Foo(list_d, s);
这也看起来类型安全,因为你作为调用者不一定知道Foo将如何处理它的参数。
因此,在这种情况下,您有两个看似类型安全的代码,这些代码最终都是类型不安全的。这很糟糕,因为它是隐藏的,因此难以避免并且难以调试。
答案 1 :(得分:8)
考虑一下......
List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException
您可以将任何父类型的内容添加到列表中,这可能不是以前声明的内容,如上例所示,会导致各种问题。因此,这是不允许的。
答案 2 :(得分:4)
由于仿制药的工作方式,它们不是彼此的子类型。你想要的是声明你的功能:
public void wahey(List<?> list) {}
然后它将接受扩展Object的任何内容的List。你也可以这样做:
public void wahey(List<? extends Number> list) {}
这将允许您列出Number的子类的列表。
我建议你拿一份Maurice Naftalin&amp; amp;的“Java Generics and Collections”。菲利普瓦德勒。
答案 3 :(得分:3)
这里基本上有两个抽象维度,列表抽象和内容的抽象。沿列表抽象变化是完全正确的 - 例如,它是一个LinkedList或一个ArrayList - 但是进一步限制内容并不好说:这个(保存对象的列表)是一个(链表,只持有数字)。因为任何将其知道为(包含对象的列表)的引用都可以通过其类型的契约理解它可以保存任何对象。
这与您在非泛型示例代码中所做的完全不同,您已经说过:将此String视为Double。你试图说:把这个(仅包含数字的列表)视为(包含任何东西的列表)。它没有,并且编译器可以检测到它,所以它不会让你逃脱它。
答案 4 :(得分:1)
“我们在这里做的基本上是 同样的事情,除了这将通过 编译时检查,但只能失败 运行。带有列表的版本不会 编译“。
当您认为Java泛型的主要目的是在编译时而不是运行时使类型不兼容失败时,您所观察到的内容非常有意义。
泛型为您提供了一种方式 传达集合的类型 到编译器,以便它可以 检查。一旦编译器知道了 元素类型的集合, 编译器可以检查你是否使用过 集合一致,可以 在值上插入正确的强制转换 被收集出来。
答案 5 :(得分:0)
在Java中,当List<S>
是List<T>
的子类型时,S
不是 T
的子类型。此规则提供类型安全性。
我们假设我们允许List<String>
成为List<Object>
的子类型。请考虑以下示例:
public void foo(List<Object> objects) {
objects.add(new Integer(42));
}
List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);
因此,强制这样可以提供类型安全,但它也有缺点,它的灵活性较差。请考虑以下示例:
class MyCollection<T> {
public void addAll(Collection<T> otherCollection) {
...
}
}
这里有一个T
的集合,您想要添加其他集合中的所有项目。对于Collection<S>
S
T
子类型,您无法使用class MyCollection<T> {
// Now we allow all types S that are a subtype of T
public void addAll(Collection<? extends T> otherCollection) {
...
otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
}
}
来调用此方法。理想情况下,这是可以的,因为您只是在集合中添加元素,而不是修改参数集合。
为了解决这个问题,Java提供了他们所称的&#34;通配符&#34;。通配符是一种提供协方差/逆变的方法。现在考虑以下使用通配符:
{{1}}
现在,通过通配符,我们允许类型T中的协方差,并且我们阻止非类型安全的操作(例如将项添加到集合中)。这样我们就可以获得灵活性和类型安全性。