Java泛型和继承混淆 [英] Java generics and inheritence confusion

查看:119
本文介绍了Java泛型和继承混淆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Kathy Sierra的关于SCJP的书中,我们了解到,当我们写作

  List< A> list = new ArrayList< A> (); 

这意味着此列表只接受类型A的元素,而不是子类型或超类型, A.然后我遇到了这个例子

  import java.util。*; 

接口A {
public void a();
}
class B implements A {
public void a(){}
public void b(){}
}

class C扩展B {
public void a(){}
}

class D extends C {

}

公共类泛型{
public static void main(){
List< B> lst = new ArrayList< B> ();
lst.add(new B());
lst.add(new C());
lst.add(new D());






$ b

这里列表已经用B声明,因为它是绑定的类型。但它看起来也是接受它的子类型的对象。为什么这样?如果可能,那么这个声明是否可用于java?

  List< ;?扩展B> list = new ArrayList< B> (); 

现在使用这种表示法,我们告诉编译器,允许任何扩展B的类型进入这个列表。请帮忙,我很困惑

解决方案

不是规则。



随着泛型的出现,编译器还没有被人类知识指导,所以它不知道列表< A> 元素的有序序列(它可以是 proboscidean ),因此它不能保证列表接受类型 A 元素仅限$ c>。所有编译器知道的是由以下定义提供的:

  interface列表< E> {
public void add(E);
public E get(int index);

$ / code>

这段代码没有定义单一类型:它定义了一系列无限的类型:
$ b

接口ListOfStrings {
public void add(String s);
public String get(int index);
}

接口ListOfShapes {
public void add(Shape e);
public Shape get(int index);
}

,具体取决于您如何实例化参数化类型。由于在Java中每个子类型都是它的超类型的有效替换,因此 Rectangle List< Shape> ' add(Shape s)



这是当人们认为泛型时,我终于找到你了!。然后,他激发一个IDE,类型为
$ b

  List< Shape> shapes = new LinkedList< Rectangle>(); 

并且编译器拒绝编译。 - 这里发生了什么?我可以将rect添加到形状列表中,但是rects列表不是形状列表? 。问题是人们用列表,形状和矩形来思考,但编译器不知道其中的任何一个。它看到

  T reference =<返回类型S的表达式> 

所以它奇观 - 是 S 有效替换类型 T 。换句话说,根据Java规则构造的类型层次结构中的 T S 的父项。所以它仔细检查它的旧Java书籍,发现不是, X< A> 不是 X< B> 家长。非法代码。 Stop

- 但是矩形列表绝对包含形状!我刚刚编译了一个程序,它将一些矩形添加到列表与LT;形状> <!/ code>。这对编译器来说并不意味着什么,它不是一个很聪明的程序 - 你会看到......它只能解释几年前它学到的几条规则,甚至无法分辨出列表猛犸象。它只知道它的旧Java书,并且在书中明确陈述并指出 X X ,无论 A B 如何相互关联即可。

java prettyprint-override> String [] strings = new String [10];
Object [] objects = strings;
objects [0] = new Rectangle(); // ArrayStoreException在运行时

也许你现在已经明白了。毕竟,那些Java人并不是那么愚蠢......至少,他们为他们的类型系统选择的那些规则起到了一个目的,至少他们是出于实际原因这样做的。他们创建了数组协变,但泛型不变。由于数组和列表的内容都可以改变(它们是 mutable ),所以这暴露了上述问题的数组(但不是列表)。例如, Scala 通过使列表协变但不可变(a List [Integer] 可以分配给一个 List [Number] ,但它们是只读的)和数组可变但不变的(你不能使用 Array [Integer] 其中需要 Array [Number] ,但您可以修改其内容)。


$ b $ p

最后,将 A B 之间的子类型关系移植到泛型世界,可以使用通配符,但这对于一个全新的故事很重要。我只是预测,在旧的Java书中写道: List< Rectangle> 不会扩展 List< Shape> ,但它是 List< ;?的合法孩子扩展形状>


In Kathy Sierra's book on SCJP we learn that, When we write

List <A> list = new ArrayList <A> ();

This means that this list will accept elements of type A only, not it's subtype or supertype, only type A. Then I came across this example

import java.util.*;

interface A {
    public void a();
}
class B implements A {
    public void a() { }
    public void b() { }
}

class C extends B {
    public void a() { }
}

class D extends C {

}

public class Generics {
    public static void main() {
        List <B> lst = new ArrayList <B> ();
        lst.add(new B());
        lst.add(new C());
        lst.add(new D());
    }
}

Here a list has been declared with B as it's bound type. But it looks like it's accepting objects of it's subtype also. Why is it so? And if it is possible then is this declaration available in java

List <? extends B> list = new ArrayList <B> ();

Now with this notation we tell compiler that any type which extends B is allowed to enter this list. Please help, I am really confused

解决方案

The problem in teaching generics with the List examples is that one can understand the examples and still not the rule.

With the advent of generics, the compiler hasn't been instructed with human knowlege, so it doesn't know that a List<A> is an ordered sequence of elements (it could be a proboscidean with long, curved tusks), and thus it can't guarantee that a List will accept elements of type A only. All the compiler knows is supplied by the following definition

interface List<E> {
  public void add(E);
  public E get(int index);
}

This code does not define a single type: it defines an infinite series of types:

interface ListOfStrings {
  public void add(String s);
  public String get(int index);
}

interface ListOfShapes {
  public void add(Shape e);
  public Shape get(int index);
}

depending on how you instantiate the parameterized type. Since in Java each subtype is a valid replacement for its supertypes, a Rectangle is a legal argument to a List<Shape>'s add(Shape s).

Here is when one thinks "Generics, I finally got you!". Then he fires an IDE, types

List<Shape> shapes = new LinkedList<Rectangle>();

and the compiler refuses to compile. "- What's going on here? I can add a rect to a list of shapes, but a list of rects is not a list of shapes?". The problem is one thinks in terms of lists, shapes and rectangles, but the compiler doesn't know any of these. It sees

T reference = <expression returning type S>

so it wonders "- Is S a valid replacement for type T?". Phrased differently, is T a parent of S in the type hierarchy, as constructed according to the Java rules? So it carefully checks its old Java book and finds that no, X<A>is not a X<B> parent. Illegal code. Stop.

"- But a list of rectangles definitely contains shapes! I just compiled a program which added a bunch of rectangles to a List<Shape>!". This doesn't mean anything to a compiler, it's not such a smart program - you see... It can only interpret those few rules it learned some years ago and can't even tell the difference between a List and a Mammoth. It only knows its old Java book, and in the book is clearly stated and remarked that there is no subtyping relation between X<A> and X<B>, no matter how A and B relate to each other. "- If only generics were implemented like arrays... You, stupid Java people..." In fact:

String[] strings = new String[10];
Object[] objects = strings;
objects[0] = new Rectangle(); // ArrayStoreException at runtime

Maybe you now got the point. After all, those Java people are not so stupid... At least, those fancy rules they chosen for their type system serve a purpose, at least they did it for a practical reason. They made arrays covariant, but generics invariant. Since both arrays and lists' contents can be changed (they are mutable), this exposes arrays (but not lists) to the problem seen above. For example, Scala achieves type safety by making lists covariant but immutable (a List[Integer] can be assigned to a List[Number], but they're read-only) and arrays mutable but invariant (you can't use an Array[Integer] where an Array[Number] is required, but you can modify their contents).

Finally, to port the subtyping relation between A and B into the generics world, one can use the wildcard ?, but that's matter for a whole new story. I just anticipate that in the old Java book is written that List<Rectangle> does not extends List<Shape>, but it's a legitimate child of List<? extends Shape>.

这篇关于Java泛型和继承混淆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆