【问题标题】:Java inheritance vs initializationJava继承与初始化
【发布时间】:2015-06-26 14:40:32
【问题描述】:

我正在阅读 J. Bloch 的 Effective Java,现在我正在阅读继承与组合部分。据我了解,他说继承并不总是好的。

子类脆弱的一个相关原因是它们的超类 可以在后续版本中获取新方法。假设一个程序 其安全性取决于所有元素插入到 一些集合满足一些谓词。这可以通过 来保证 继承集合并覆盖每个方法 添加一个元素以确保谓词之前得到满足 添加元素。在一种新方法能够 插入元素被添加到超类中 释放。

但为什么它不起作用?超类只是Collection 接口,如果我们添加一个新方法,我们只是一个编译时错误。这永远不会有害...

【问题讨论】:

  • 向超添加新方法不会导致这样的编译时错误。事实上,将default 方法添加到接口也不会产生编译时错误。
  • 如果你说的是java.util.Collection,那不是超类。那只是一个界面。
  • 在超类或接口中添加抽象方法,如果重新编译子类,会出现编译时错误,但这可能不会发生。

标签: java inheritance


【解决方案1】:

假设您在某个库 v1.0 中有一个 Collection 超类:

public class MyCollection {
    public void add(String s) {
        // add to inner array
    }
}

您将其子类化以便只接受长度为 5 的字符串:

public class LimitedLengthCollection extends MyCollection {
    @Override
    public void add(String s) {
        if (s.length() == 5) {
            super.add(s);
        }
    }
}

契约,这个类的不变量是它永远不会包含长度不为 5 的字符串。

现在该库的 2.0 版已发布,您可以开始使用它。基类修改为:

public class MyCollection {
    public void add(String s) {
        // add to inner array
    }

    public void addMany(String[] s) {
        // iterate on each element and add it to inner array
    }
}

并且您的子类保持不变。现在你的子类的用户可以做

LimitedLengthCollection c = new LimitedLengthCollection();
c.addMany(new String[] {"a", "b", "c"});

因此,您的子类的合同被打破了。它原本应该只接受长度为 5 的字符串,现在它不再接受了,因为在超类中添加了一个额外的方法。

【讨论】:

  • 虽然这是问题的核心(脆弱的基类),但仅当addMany 方法使用add 方法但实现了添加行为本身。也许您应该为所有这些方法添加一些实现细节。
  • 正确。这将导致另一个问题。 2.0 版可以委托给 add(),您可以依靠它仅在 add() 中进行检查,而 3.0 版不再委托给 add(),因此您的检查将被绕过。
  • 所以,这里的重点是MyCollection是一个具体的类。知道了。但是在 java 8 中,实现库的接口仍然安全吗?它们可以为某些方法提供默认实现,如果我们在 1.0 版本和 2.0 版本中实现其中一个方法,则在默认实现中添加了某些方法,我们也可能会遇到麻烦(正如您指出的不变量)。
  • @St.Antario 接口确实没有这个问题。
  • 即使在默认实现的 Java 8 中?
【解决方案2】:

问题不在于继承不起作用。

问题在于,通过继承,开发人员无法强制执行某些行为(例如满足某些谓词的集合示例)。

当我们很少创建一个新类时,它实际上是另一个类的特殊类型。更多时候,使用其他类是新事物。

所以我们很少需要继承,更多时候我们需要创建一个使用其他类的类。

IS A vs HAS A

你必须问自己:

B 类是 A 类的一个新子类型,它以不同的方式做与 A 相同的事情?

B 类内部有一个类可以做一些不同的事情 A 打算做什么?

并且知道更多时候正确答案是后者。

【讨论】:

    【解决方案3】:

    如果我们添加一个新方法,我们只是一个编译时错误

    只有在将 abstract 方法添加到超类/接口时才会如此。如果添加了非抽象方法,那么不覆盖该新方法是完全有效的。

    【讨论】:

      【解决方案4】:

      因为它(通常)会破坏已实现 Collection 类的客户端代码。

      在此特定示例中,安全性将被破坏,因为恶意用户将能够使用在您发布代码后添加的尚未覆盖的方法插入项目。

      将您的代码基于您无法控制的继承类可能会在将来对您不利。

      【讨论】:

      • 在这种情况下这将是一件好事;如果客户端代码中断(无法编译),则需要对其进行修复才能运行,这意味着避免了安全威胁。
      • 你能想象 Oracle 在以后改变 Collection 类/接口吗?那会有什么问题?
      • 我不同意这样改变公共界面是一件坏事。我只是指出这个问题是关于安全问题的,在这种情况下,代码中断比引起安全问题更可取。
      • 查看我的编辑。如果您已发送代码,则存在安全问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多