默认方法是我们Java工具箱中的一个很好的新工具。但是,我试图编写一个定义一个 default 版本的 toString 方法。Java告诉我这是被禁止的,因为方法在 java.lang.Object 可能不是 default艾迪为什么会这样?

我知道有"基类总是赢"规则,所以默认情况下(双关语;),任何 default 实施 Object 方法将被复盖的方法从 Object 不管怎样。但是,我认为没有理由为什么不应该有一个异常的方法从 Object 在规范中。特别是对于 toString 拥有默认实现可能非常有用。

那么,Java设计者决定不允许的原因是什么呢? default 方法重写方法 Object?

有帮助吗?

解决方案

这是另一个语言设计问题,似乎"显然是一个好主意",直到你开始挖掘,你意识到它实际上是一个坏主意。

这封邮件 在这个问题上有很多(也在其他问题上)。 有几种设计力量汇聚在一起,使我们进入当前的设计:

  • 保持继承模型简单的愿望;
  • 事实上,一旦你看过去的明显的例子(例如,转 AbstractList 成一个接口),你意识到继承equals/hashCode/toString强绑定到单继承和状态,接口是多重继承和无状态的;
  • 它可能为一些令人惊讶的行为打开了大门。

你已经触及了"保持简单"的目标;继承和冲突解决规则的设计非常简单(类战胜接口,派生接口战胜超接口,以及任何其他冲突都由实现类解决。)当然,这些规则可以被调整以使一个例外,但我认为当你开始拉动该字符串时,你会发现增量复杂性并不像你想象的那么小。

当然,有一定程度的好处可以证明更多的复杂性,但在这种情况下,它并不存在。我们在这里谈论的方法是equals,hashCode和toString。这些方法本质上都是关于对象状态的,它是拥有状态的类,而不是接口,谁处于确定该类的平等意味着什么的最佳位置(特别是作为平等的契约是相当强;一些令人惊讶的后果见Effective Java);界面编写器只是太远了。

很容易拔出 AbstractList 例子:;如果我们能摆脱它,那就太好了 AbstractList 并将行为放入 List 界面。但是,一旦你超越了这个明显的例子,就没有很多其他好的例子可以找到。在根, AbstractList 是为单一继承而设计的。但是接口必须设计用于多重继承。

进一步,想象你正在写这个类:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

Foo writer查看了超类型,没有看到equals的实现,并得出结论,要获得引用平等,他需要做的就是继承equals from Object.然后,下周,Bar的库维护者"有益地"添加了一个默认值 equals 执行。哎呀!现在的语义 Foo 已被另一个维护域中的接口"有益地"为通用方法添加默认值。

默认值应该是默认值。将默认值添加到没有(层次结构中的任何位置)的接口不应影响具体实现类的语义。但是,如果默认值可以"复盖"对象方法,那就不是真的。

所以,虽然它看起来是一个无害的功能,但实际上它是相当有害的:它增加了很多复杂性,增加了很少的增量表达,并且它使得对单独编译的接口进行善意的,无害的更改太容易破坏实现类的预期语义。

其他提示

禁止在接口中定义默认方法 java.lang.Object, ,因为默认方法永远不会"可达"。

默认接口方法可以在实现接口的类中被复盖,并且该方法的类实现具有比接口实现更高的优先级,即使该方法是在超类中实现的。由于所有类都继承自 java.lang.Object, ,方法在 java.lang.Object 将优先于接口中的默认方法,并被调用。

来自Oracle的Brian Goetz在此提供了有关设计决策的更多细节 邮寄名单.

我没有看到Java语言作者的头,所以我们只能猜测。但我看到很多原因,并同意他们绝对在这个问题上。

引入默认方法的主要原因是能够在不破坏旧实现的向后兼容性的情况下向接口添加新方法。默认方法也可以用来提供"方便"的方法,而不需要在每个实现类中定义它们。

这些都不适用于Tostring和Object的其他方法。简而言之,默认方法旨在提供 违约情况 没有其他定义的行为。不提供将与其他现有实现"竞争"的实现。

"基类总是赢"规则也有其坚实的理由。它应该是类定义的 真实的 实现,而接口定义 违约情况 实现,这有点弱。

此外,对一般规则引入任何例外都会造成不必要的复杂性并引发其他问题。Object和其他类一样(或多或少)是一个类,那么为什么它应该有不同的行为呢?

总而言之,您提出的解决方案可能会带来更多的缺点而不是优点。

推理很简单,这是因为Object是所有Java类的基类。因此,即使我们在某些接口中将Object的方法定义为默认方法,它也是无用的,因为Object的方法将始终被使用。这就是为什么为了避免混淆,我们不能有复盖对象类方法的默认方法。

给出一个非常迂腐的答案,只禁止定义一个 default 一种方法 公众人士 方法从 java.lang.Object.有11种方法需要考虑,可以分为三种方法来回答这个问题。

  1. 六个 Object 方法不能有 default 方法,因为它们是 final 并且根本无法复盖: getClass(), notify(), notifyAll(), wait(), wait(long), ,而 wait(long, int).
  2. 三个 Object 方法不能有 default Brian Goetz上面给出的原因的方法: equals(Object), hashCode(), ,而 toString().
  3. 两个 Object 方法 可以default 方法,尽管这样的默认值充其量是值得怀疑的: clone()finalize().

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }
    
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top