Java 为什么列表<;数量>;不是列表的子类型<;对象>;?

Java 为什么列表<;数量>;不是列表的子类型<;对象>;?,java,generics,casting,covariance,Java,Generics,Casting,Covariance,我们在这里所做的基本上是相同的,只是这将通过编译时检查,并且只在运行时失败。无法编译包含列表的版本 这让我相信这纯粹是一个关于泛型类型限制的设计决策。我希望得到一些关于这个决定的意见?考虑一下是否 Object o; Double d; String s; o = s; d = (Double) o; List nums=new ArrayList(); 列表objs=nums 添加(“哦,不!”); int x=nums.get(0)//抛出类CastException 您可以将父类型的

我们在这里所做的基本上是相同的,只是这将通过编译时检查,并且只在运行时失败。无法编译包含列表的版本

这让我相信这纯粹是一个关于泛型类型限制的设计决策。我希望得到一些关于这个决定的意见?

考虑一下是否

Object o;
Double d;
String s;

o = s;
d = (Double) o;
List nums=new ArrayList();
列表objs=nums
添加(“哦,不!”);
int x=nums.get(0)//抛出类CastException
您可以将父类型的任何内容添加到列表中,这可能不是以前声明的内容,正如上面的示例所示,这会导致各种问题。因此,这是不允许的。

您在“无泛型”示例中所做的是一个强制转换,这表明您正在做一些类型不安全的事情。与泛型相同的是:

List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException
对象o;
名单d;
字符串s;
o=s;
d、 加((双)o);
其行为方式相同(编译,但在运行时失败)。不允许您询问的行为的原因是,它将允许隐式类型的不安全操作,这在代码中很难注意到。例如:

Object o;
List<Double> d;
String s;

o = s;
d.add((Double) o);
public void Foo(列表,对象对象){
列表。添加(obj);
}
这看起来非常好,而且类型安全,直到您这样称呼它:

public void Foo(List<Object> list, Object obj) {
  list.add(obj);
}
List<Double> list_d;
String s;

Foo(list_d, s);
List\u d;
字符串s;
Foo(列表d,s);
这看起来也是类型安全的,因为作为调用者,您不一定知道Foo将如何处理其参数


因此,在这种情况下,您有两个看似类型安全的代码位,这两个位加在一起最终是类型不安全的。这很糟糕,因为它是隐藏的,因此很难避免,也很难调试。

由于泛型的工作方式,它们不是彼此的子类型。您想要的是像这样声明您的函数:

public void Foo(List<Object> list, Object obj) {
  list.add(obj);
}
List<Double> list_d;
String s;

Foo(list_d, s);
publicsvoidwahey(列表){
然后它将接受扩展对象的任何内容的列表。您还可以执行以下操作:

public void wahey(List<?> list) {}

public void wahey(List这里基本上有两个抽象的维度,列表抽象和它的内容的抽象。沿着列表抽象变化是很好的,比如说,它是一个LinkedList或ArrayList,但是进一步限制内容是不好的,比如说:(包含对象的列表)是一个(只包含数字的链表)。因为任何将其理解为(包含对象的列表)的引用,通过其类型的契约,都理解它可以包含任何对象

这与您在非泛型示例代码中所做的完全不同,在非泛型示例代码中,您说过:将此字符串视为Double。相反,您试图说:将此(仅包含数字的列表)视为(包含任何内容的列表)。它不包含任何内容,编译器可以检测到它,因此它不会让您得逞

“我们在这里所做的基本上是 同样的事情,只是这会过去 编译时检查,仅在 运行时。包含列表的版本将不会 编译。”

您认为,java泛型的主要目的是在编译时而不是运行时获得类型不兼容,这是完全正确的。 从

泛型为您提供了一种 传递集合的类型 到编译器,以便 选中。一旦编译器知道 集合的元素类型 编译器可以检查您是否使用了 该系列始终保持一致,并且可以 在值上插入正确的强制转换 被带出藏品

在Java中,
List
不是
List
的子类型,而
S
T
的子类型。此规则提供类型安全性

让我们允许<代码>列表>代码>是代码>列表的子类型。考虑下面的例子:

public void wahey(List<? extends Number> list) {}
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);

现在,使用通配符,我们允许类型T中的协方差,并阻止类型不安全的操作(例如,将项添加到集合中)。这样我们可以获得灵活性和类型安全性。

这是正确的,但问题表明他已经理解了这一点,并且想知道为什么决定禁止这种行为,同时仍然允许其他形式的类型不安全行为。我已经理解了这个示例。我试图在问题中解释这种情况(正如泰勒所说),但谢谢你的评论。这种类型的不安全行为实际上是允许的。下面的编译很好,但会生成一个运行时错误:
Object[]arr=new Integer[2];arr[0]=“噢,不!”;
相关(虽然正确答案基本相同,但不是完全重复):art的术语是协方差——我之所以提到它,是因为它可以让您更容易地查找它,包括堆栈溢出(如果您想了解我对它的看法——在C#的上下文中,但它也适用于Java——例如,搜索[Convariance user:95810])。类似于
wahey((List)(List)new ArrayList()的双重强制转换
将编译,尽管不建议这样做。啊,我明白了。非常感谢。:/1,泛型就是这样工作的。一定要买一本Java泛型和集合的副本。很棒的书!这不是我的问题。我知道这些是泛型子类型的规则。
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) {
        ...
    }
}
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)
     }
}