【问题标题】:Which type safety would have lost, had Generics supported the sub-typing? [duplicate]如果泛型支持子类型,哪种类型安全会丢失? [复制]
【发布时间】:2016-06-11 10:41:30
【问题描述】:

考虑sn-p:

Number[] numbers = {1, 2.3, 4.5f, 6000000000000000000L};

上面做完全没问题,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));

如果第 3 行被设计为编译成功, 我们最终会得到ListNumbers,即,

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

// 10
// 1.23

都是数字。

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

泛型不支持子类型,因为它会导致 实现类型安全。这就是为什么List&lt;T&gt; 不被视为 List&lt;S&gt; 的子类型,其中 ST 的超类型

在上面讨论的这种特定情况下,第 3 行是否会成功编译?

【问题讨论】:

  • 查看这篇文章,了解为什么使用 Java generics are not covariant
  • List&lt;? extends Number&gt; 可以。它可以防止add

标签: java generics


【解决方案1】:
List<Long> listLong = new ArrayList<Long>();
List<Number> listNumbers = listLong;

那么,listNumberslistLong 将是对同一个列表的两个引用,如果可能的话,对吧?

listNumbers.add(Double.valueOf(1.23));

因此,您可以将 Double 添加到该列表中。 listLong,类型为 List&lt;Long&gt;,因此将包含一个 Double。类型安全将因此被破坏。

【讨论】:

  • 我今天早上怎么这么笨?
  • @ShirgillFarhanAnsari 你不是,这实际上是一个好问题 :)
  • @ShirgillFarhanAnsari 在 C# 中,他们为此创建了 inout(通用修饰符)。如果您想知道避免此答案中指出的问题的策略,请查找它。
  • 这当然是一个很好的问题,并且有一点令人困惑可能是因为数组不是不变的。这是一个错误:它们应该是不变的,因为会发生完全相同的问题 (long[] longs = { 1, 2 }; Number[] nums = longs; nums[0] = 0.5; })。但是,当 Java 没有泛型时,数组是传递类型化集合的唯一方法,因此他们希望 something 是协变的。这就是生活。
  • @svick 基本上不同之处在于您在哪里声明了协方差。 C# 具有声明站点差异,而 Java 具有使用站点差异。使用集声明更强大(避免大量代码重复),但同时对普通用户来说更容易混淆。尽管让我很痛苦,但声明站点差异可能是主流语言的更好方法。
【解决方案2】:

如果是这样,那么我们可以将Number 的其他不同子类型添加到listNumbers 中,这是必须禁止的。

假设您现在插入DoubleLong 类型的对象,然后您尝试使用Long#reverse。您的代码编译,但当然会在运行时失败(糟糕)第一个Double它会通过。

【讨论】:

    【解决方案3】:

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

    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) {
            // ...
        }
    }
    

    在任何需要Human 的地方,StudentTeacher 都可以,因为它们完全实现了Human 接口。 (在这种情况下,可以对它们调用 getName()。)Java 继承保证了技术上的情况。使其在语义上工作是类作者的工作,以便他的代码满足Liskov substitution principle

    这是否意味着我们也可以替换 Collection&lt;Teacher&gt; 以替换 Collection&lt;Human&gt; 预期?不总是。考虑以下方法:

    public class Human {
        // ...
    
        public void join(Set<Human> party) {
            party.add(this);
        }
    }
    

    现在,如果 Java 允许 Set&lt;Student&gt; 作为派对传递,任何非Student Humans 加入该派对的尝试都必须在运行时失败。

    作为一般规则,如果接收者(函数参数的被调用者,函数返回值的调用者)想要将某些东西放入其中,则子类型的容器是不合适的,但如果接收者只想取出东西并使用它,则可以接受。 超类型容器如果接收者想要取出东西并使用它是不合适的,但如果接收者只将东西放入其中则可以接受。因此,如果接收者既从集合中取出东西又将东西放入集合中,它们通常必须需要一个固定类型的集合。

    我们的join 方法仅将Humans 放入party,因此我们还可以允许Set&lt;Object&gt; 或非泛型Set 或等效的Set&lt;?&gt;。 Java 允许我们通过 lower-bounded wildcards 做到这一点:

    public class Human {
        // ...
    
        public void join(Set<? super Human> party) {
            party.add(this);
        }
    }
    

    为了打开子类的可能性,有upper-bounded wildcards

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

    现在,如果我们将Student 子类化,则传递的schoolClass 也可以是该子类型的Set

    【讨论】:

      【解决方案4】:

      你指的概念是variance

      换句话说,如果ST 的超类型,那么List&lt;S&gt; 是子类型、超类型、相等类型还是与List&lt;T&gt; 无关?

      List 和所有其他 Java 泛型* 的答案是“不相关的”,即不变。

      class SuperType {}
      
      class Type extends SuperType {}
      
      class SubType extends Type {}
      
      List<Type> list = ...
      
      List<SuperType> superList = list;
      superList.add(new SuperType());
      // no, we shouldn't be able to add a SuperType to list
      
      List<SubType> subList = list;
      SubType item = subList.get(0);
      // no, there's not necessarily only SubType items in list
      

      *Java 确实具有“使用站点”差异的概念,wildcards (?)。这将限制可以调用的方法。

      List<Type> list = ...
      
      List<? super SubType> wildcardList = list;
      wildcardList.add(new SubType());
      // but...everything we get() is an Object
      

      List<Type> list = ...
      
      List<? extends SuperType> wildcardList = list;
      SuperType item = wildcard.get(0);
      // but...it's impossible to add()
      

      仅供参考,某些语言具有定义站点差异的概念,例如斯卡拉。所以List[Int] 确实是List[Number] 的子类型。这对于不可变集合是可能的(同样,一组有限的方法),但显然不适用于可变集合。

      【讨论】:

        猜你喜欢
        • 2013-11-17
        • 2019-05-26
        • 2018-08-26
        • 1970-01-01
        • 2020-07-09
        • 1970-01-01
        • 2018-04-17
        • 2020-07-21
        • 1970-01-01
        相关资源
        最近更新 更多