为什么 A->B 不会产生 List->List?这样不是可以消除通配符的需要吗?
为什么 A->B 不会产生 List->List?这样不是可以消除通配符的需要吗?
免责声明:我不是专业开发人员,并且也没有打算成为一个。我正在阅读有关Java的书籍,因为我想尝试Android编程,没有任何Java经验。
我正在阅读这本书,我相当喜欢它。我已经读了关于泛型类的一部分章节,看到了他们提到通配符,我感到困惑。
如果B扩展A:
后者允许编写接受泛型类型参数的函数,例如List extends A>
。这样的函数将接受List
或List
类型的参数。
现在,我的问题是:
是不是采用类似C++(以“模板”方式)的方式实现泛型会更简单?这将使List
和List
成为两个不同的类型,它们之间以预期的方式相关。这也可以简单地声明一个函数,指明你期望的参数类型是List
,这样List
也能很好地适应。
我猜这背后不仅仅是“我们讨厌C++,让我们把事情做得不同”。很可能我还不知道一些使通配符成为一种奇妙而有用的工具的东西。你对此有什么看法?
编辑:如果你在回答中提到List
,请记得使用反引号,以避免
被解释为HTML标签。
如果List是List的子类型,那么上述代码将是合法的,因为Java在编译时会删除大部分泛型的魔力。(这是否是一个好决定是另一个话题)。
但是,这样的设计会导致类型安全性问题。考虑以下示例:
public void addNewAToList(List list) { list.add(new A()); } public static void main(String[] args) { List listB = new LinkedList(); addNewAToList(listB); // <-- 编译错误 for (B b : listB) { // <-- 否则:列表中会有一个A。 System.out.println(b); } }
在上述代码中,我们尝试将一个A对象添加到一个List中,这是不合法的。如果允许List是List的子类型,那么编译器将无法在编译时检测到这个错误。
因此,Java不允许将List视为List的子类型。这就是为什么A->B不会导致List->List的原因。
解决这个问题的一种方法是使用通配符(wildcard)。通过使用通配符,我们可以灵活地处理不同类型的列表。
例如,可以将方法定义为接受List extends A>,这意味着它可以接受任何类型为A或A的子类型的列表。然后,我们可以在方法中安全地添加A或其子类型的对象,而不会破坏类型安全性。
以下是修改后的代码示例:
public void addNewAToList(List extends A> list) { list.add(new A()); // <-- 编译错误 } public static void main(String[] args) { List listB = new LinkedList(); addNewAToList(listB); for (B b : listB) { System.out.println(b); } }
在上述代码中,我们使用了通配符来表示列表的类型,并且在方法中尝试将一个A对象添加到列表中。这时,编译器会报错,因为它无法确定列表的确切类型,从而保证了类型安全性。
通过使用通配符,我们可以在不破坏类型安全性的情况下处理不同类型的列表。这是解决A->B不导致List->List的一种方法。
问题在于如果你有一个`class C extends A`并且给定一个`List`,你可以将一个`C`放入其中,因为`C`是`A`的一个子类。但是如果Java允许你只给方法一个`List`,那么你就将一个`C`放入了一个`B`的列表中。而`C`不是`B`,所以会在后面出现错误。Java的通配符解决方案只允许你向`List extends A>`中添加`null`,从而避免这个错误。
解决方法是使用通配符。通过在声明列表类型时使用通配符,可以使列表具有更灵活的类型。使用`List extends A>`表示列表中的元素是`A`或`A`的子类,这样就可以安全地将`C`放入`List extends A>`中。
然而,使用通配符也有一些限制。由于编译器无法确定通配符的具体类型,因此不能向`List extends A>`中添加任何非`null`元素。因此,通配符并不能完全解决问题,但它可以帮助我们避免将错误类型的元素添加到列表中。
Java不允许`A->B`的转换使得`List->List`成立,是为了避免将错误类型的元素添加到列表中。而通配符可以帮助我们在一定程度上解决这个问题,但也有一些限制。
为什么A->B无法使List->List?这会不会消除对通配符的需求?
这个问题的出现是因为下面的原因。假设你有一个类型为List的变量。假设List确实是List的子类型。
这意味着以下代码是合法的:
List a_list;
a_list = new List(); //当List是List的子类型时允许这样写
a_list.add(new A()); //糟糕!
在上面的代码中,我们向a_list中添加了一个类型为A的项。由于a_list被声明为List,这应该是合法的。但是等等:a_list指向的是一个类型为List的东西。
所以现在我们向只能存储类型为B的项的列表中添加了一个类型为A的项,这显然不是我们想要的,因为A不是B的子类!
然而,当你尝试使用数组时,它会编译通过,但在运行时遇到WOAH!时会抛出异常。数组至少在尝试将错误类型的项添加到它们时会抛出异常... 而像List这样的泛型结构只有在你尝试从中读取并将其转换为它不是的类型时才会抛出异常。
这就是泛型通过类型擦除带来的问题。为什么他们扩展了JVM字节码以支持断言,而没有扩展以支持泛型呢?