Java 8最有用的特性之一是接口上新的默认方法。它们被引入的主要原因有两个(可能还有其他原因):

提供实际的默认实现。例如:Iterator.remove () 允许JDK API的发展。例如:Iterable.forEach ()

从API设计者的角度来看,我希望能够在接口方法上使用其他修饰符,例如final。这在添加方便方法时非常有用,可以防止在实现类时发生“意外”重写:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

如果Sender是一个类,上面已经是常见的做法:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

现在,default和final显然是相互矛盾的关键字,但default关键字本身并不是严格要求的,所以我假设这种矛盾是故意的,以反映“带有主体的类方法”(只是方法)和“带有主体的接口方法”(默认方法)之间的微妙差异,即我还没有理解的差异。

在某些时候,对接口方法上的修饰符(如static和final)的支持还没有完全开发出来,引用Brian Goetz的话:

另一部分是我们将在多大程度上支持班级建设 接口中的工具,如final方法、私有方法、受保护 方法,静态方法等等。答案是:我们还不知道

显然,从2011年末开始,接口中增加了对静态方法的支持。显然,这为JDK库本身增加了很多价值,例如comparator . compare()。

问题:

final(也是静态final)从来没有出现在Java 8接口中的原因是什么?


It will be hard to find and identify "THE" answer, for the resons mentioned in the comments from @EJP : There are roughly 2 (+/- 2) people in the world who can give the definite answer at all. And in doubt, the answer might just be something like "Supporting final default methods did not seem to be worth the effort of restructuring the internal call resolution mechanisms". This is speculation, of course, but it is at least backed by subtle evidences, like this Statement (by one of the two persons) in the OpenJDK mailing list:

我想如果允许使用“final default”方法,它们可能需要从内部调用专用重写为用户可见的调用接口。

还有一些琐碎的事实,比如当一个方法是默认方法时,它就不被认为是(真正的)final方法,就像OpenJDK中的method::is_final_method方法目前实现的那样。

Further really "authorative" information is indeed hard to find, even with excessive websearches and by reading commit logs. I thought that it might be related to potential ambiguities during the resolution of interface method calls with the invokeinterface instruction and and class method calls, corresponding to the invokevirtual instruction: For the invokevirtual instruction, there may be a simple vtable lookup, because the method must either be inherited from a superclass, or implemented by the class directly. In contrast to that, an invokeinterface call must examine the respective call site to find out which interface this call actually refers to (this is explained in more detail in the InterfaceCalls page of the HotSpot Wiki). However, final methods do either not get inserted into the vtable at all, or replace existing entries in the vtable (see klassVtable.cpp. Line 333), and similarly, default methods are replacing existing entries in the vtable (see klassVtable.cpp, Line 202). So the actual reason (and thus, the answer) must be hidden deeper inside the (rather complex) method call resolution mechanisms, but maybe these references will nevertheless be considered as being helpful, be it only for others that manage to derive the actual answer from that.

我不认为有必要在一个方便的接口方法上指定最终,我同意尽管它可能是有帮助的,但似乎成本超过了收益。

无论哪种方式,您应该做的是为默认方法编写适当的javadoc,准确显示该方法是什么,不允许做什么。通过这种方式,实现接口的类“不允许”更改实现,尽管没有保证。

任何人都可以编写一个遵循接口的集合,然后在方法中做一些绝对违反直觉的事情,除了编写大量的单元测试之外,没有办法保护自己免受这种情况的影响。

在lambda邮件列表中有很多关于它的讨论。其中一个似乎包含了很多关于所有这些东西的讨论:关于不同的接口方法可见性(是最终的捍卫者)。

在这个讨论中,原问题的作者塔尔登提出了与你的问题非常相似的问题:

The decision to make all interface members public was indeed an unfortunate decision. That any use of interface in internal design exposes implementation private details is a big one. It's a tough one to fix without adding some obscure or compatibility breaking nuances to the language. A compatibility break of that magnitude and potential subtlety would seen unconscionable so a solution has to exist that doesn't break existing code. Could reintroducing the 'package' keyword as an access-specifier be viable. It's absence of a specifier in an interface would imply public-access and the absence of a specifier in a class implies package-access. Which specifiers make sense in an interface is unclear - especially if, to minimise the knowledge burden on developers, we have to ensure that access-specifiers mean the same thing in both class and interface if they're present. In the absence of default methods I'd have speculated that the specifier of a member in an interface has to be at least as visible as the interface itself (so the interface can actually be implemented in all visible contexts) - with default methods that's not so certain. Has there been any clear communication as to whether this is even a possible in-scope discussion? If not, should it be held elsewhere.

最终Brian Goetz的答案是:

是的,这已经在探索中了。 但是,让我设定一些现实的期望——语言/ VM 新功能的开发需要很长时间,即使是像这样看似微不足道的功能。 现在是时候为Java SE 8提出新的语言特性了 基本上通过了。

因此,它很可能从未实现,因为它从来不是范围的一部分。它从未被及时提出以供考虑。

在另一场关于最终防守方法的激烈讨论中,布莱恩再次说道:

And you have gotten exactly what you wished for. That's exactly what this feature adds -- multiple inheritance of behavior. Of course we understand that people will use them as traits. And we've worked hard to ensure that the the model of inheritance they offer is simple and clean enough that people can get good results doing so in a broad variety of situations. We have, at the same time, chosen not to push them beyond the boundary of what works simply and cleanly, and that leads to "aw, you didn't go far enough" reactions in some case. But really, most of this thread seems to be grumbling that the glass is merely 98% full. I'll take that 98% and get on with it!

所以这强化了我的理论,那就是它根本不在他们的设计范围之内。他们所做的是提供足够的功能来处理API进化的问题。

这个问题在某种程度上与Java 8接口方法中不允许“同步”的原因有关。

The key thing to understand about default methods is that the primary design goal is interface evolution, not "turn interfaces into (mediocre) traits". While there's some overlap between the two, and we tried to be accommodating to the latter where it didn't get in the way of the former, these questions are best understood when viewed in this light. (Note too that class methods are going to be different from interface methods, no matter what the intent, by virtue of the fact that interface methods can be multiply inherited.)

默认方法的基本思想是:它是具有默认实现的接口方法,派生类可以提供更具体的实现。因为设计的中心是接口演进,所以能够以源代码兼容和二进制兼容的方式将默认方法添加到接口中是一个关键的设计目标。

对于“为什么不是最终的默认方法”的过于简单的回答是,这样主体就不仅仅是默认的实现,它将是唯一的实现。虽然这个答案有点太简单了,但它给了我们一个线索,这个问题已经朝着一个有问题的方向发展了。

final接口方法值得怀疑的另一个原因是它们为实现者制造了不可能的问题。例如,假设你有:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

在这里,一切都很好;C从a继承了foo()。现在假设B有一个默认的foo方法:

interface B { 
    default void foo() { ... }
}

现在,当我们重新编译C语言时,编译器会告诉我们它不知道要继承foo()的什么行为,所以C语言必须重写它(如果它想保留相同的行为,可以选择委托给A.super.foo())。但是,如果B默认为最终版本,而A不受C的作者控制,情况会怎样呢?现在C已经不可挽回地破碎了;如果不重写foo()它就不能编译,但是如果foo()在B中是final,它就不能重写。

This is just one example, but the point is that finality for methods is really a tool that makes more sense in the world of single-inheritance classes (generally which couple state to behavior), than to interfaces which merely contribute behavior and can be multiply inherited. It's too hard to reason about "what other interfaces might be mixed into the eventual implementor", and allowing an interface method to be final would likely cause these problems (and they would blow up not on the person who wrote the interface, but on the poor user who tries to implement it.)

Another reason to disallow them is that they wouldn't mean what you think they mean. A default implementation is only considered if the class (or its superclasses) don't provide a declaration (concrete or abstract) of the method. If a default method were final, but a superclass already implemented the method, the default would be ignored, which is probably not what the default author was expecting when declaring it final. (This inheritance behavior is a reflection of the design center for default methods -- interface evolution. It should be possible to add a default method (or a default implementation to an existing interface method) to existing interfaces that already have implementations, without changing the behavior of existing classes that implement the interface, guaranteeing that classes that already worked before default methods were added will work the same way in the presence of default methods.)

当我们知道扩展该接口的类可能覆盖也可能不覆盖我们的实现时,我们在接口内部的方法中添加default关键字。但是如果我们想添加一个不希望任何实现类重写的方法呢?我们有两个选择:

添加一个默认的final方法。 添加一个静态方法。

现在,Java说,如果我们有一个实现两个或多个接口的类,它们有一个具有完全相同方法名和签名的默认方法,即它们是重复的,那么我们需要在我们的类中提供该方法的实现。现在,对于默认的final方法,我们不能提供一个实现,我们被卡住了。这就是在接口中不使用final关键字的原因。