如果Generics支持子类型,哪种类型的安全性会丢失?

Shi*_*han 30 java generics

考虑一下片段:

Number[] numbers = {1, 2.3, 4.5f, 6000000000000000000L};
Run Code Online (Sandbox Code Playgroud)

完成上述操作Number是完全可以的,是一个抽象类.

继续前进

List<Long> listLong = new ArrayList<Long>();
listLong.add(Long.valueOf(10));

List<Number> listNumbers = listLong; // compiler error    - LINE 3

listNumbers.add(Double.valueOf(1.23));
Run Code Online (Sandbox Code Playgroud)

曾3号线被设计成可以编译成功,我们最终会得到一个ListNumberS,即

for(Number num: listNumbers ){
    System.out.println(num);
}

// 10
// 1.23
Run Code Online (Sandbox Code Playgroud)

这些都是数字.

我在一本书中看到了这个,

泛型不支持子类型,因为它会导致实现类型安全性的问题.这就是为什么List<T>不被视为一个亚型List<S>,其中S是超级型T

如上所述,在这种特殊情况下哪种类型的安全性会丢失,第3行是否需要成功编译?

JB *_*zet 42

List<Long> listLong = new ArrayList<Long>();
List<Number> listNumbers = listLong;
Run Code Online (Sandbox Code Playgroud)

所以,listNumberslistLong将是相同的列表两个引用,如果这是可能的,对不对?

listNumbers.add(Double.valueOf(1.23));
Run Code Online (Sandbox Code Playgroud)

因此,您可以在该列表中添加Double.listLong因此,类型List<Long>将包含Double.因此,类型安全将被打破.

  • @ShirgillFarhanAnsari你不是,这实际上是一个很好的问题:) (13认同)

Mar*_*oun 8

如果是这样的话,那么我们可以添加其他不同亚型NumberlistNumbers,必须禁止.

想象一下,你现在插入类型的对象DoubleLong,后来又尝试使用Long#reverse.你的代码编译,但当然会在运行时失败(坏),Double它将首先通过.


das*_*s-g 5

让我们使用一个非抽象基类的例子:

public class Human {
    public string getName() {
        // ...
    }
}

public class Student extends Human {
    public void learn(Subject subject) {
        // ...
    }
}

public class Teacher extends Human {
    public void teach(Subject subject) {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

在任何Human预期的地方,a StudentTeacher将会同样做,因为他们完全实现了Human界面.(在这种情况下,getName()可以在它们上调用它.)Java继承保证在技术上就是这种情况.使它在语义上工作是班级作者的工作,因此他的代码实现了Liskov替换原则.

所以并不这意味着我们也可以替代Collection<Teacher>其中Collection<Human>预期?不总是.请考虑以下方法:

public class Human {
    // ...

    public void join(Set<Human> party) {
        party.add(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果Java允许将a Set<Student>作为聚会传递,那么非Student Humans加入该聚会的任何尝试都必须在运行时失败.

作为一般规则,如果接收者(在函数参数的情况下为被调用者,在函数返回值的情况下为调用者)想要将某些内容放入其中,则子类型容器是不合适的,但如果接收者仅想要获取内容则可接受出来并使用它.如果接收器想要取出并使用它,那么超类型容器是不合适的,但如果接收器只是将东西放入其中,则是可接受的.因此,如果接收器从集合中取出东西并将东西放入集合中,它们通常必须要求固定类型的集合.

我们的join方法只将Humans放入party,所以我们也可以允许Set<Object>a或非泛型Set或等价a Set<?>.Java允许我们使用下限通配符来实现这一点:

public class Human {
    // ...

    public void join(Set<? super Human> party) {
        party.add(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

为了打开子类的可能性,有上边界的通配符:

public class Teacher extends Human {
    public void teach(Subject subject, Set<? extends Student> schoolClass) {
        for (Student student : class) {
            student.learn(subject);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我们是子类Student,那么传递的schoolClass也可以是Set该子类型的一个.