为什么接口中的所有字段都隐式地是静态和不可变的?

105

我只是想了解为什么接口中定义的所有字段都是隐式的 staticfinal。将字段保持 static 的想法对我来说很有意义,因为您无法拥有接口的对象,但是它们为什么是 final(隐式)?

有人知道为什么Java设计者选择使接口中的字段具有 staticfinal 吗?


作为自己的一条笔记:它是静态的,因为接口的字段不会成为实现它的对象的一部分。 - VimNing
7个回答

133

接口的目的是指定交互契约,而不是实现细节。开发人员应该能够通过查看接口来使用实现,而不必查看实现它的类的内部。

接口不允许您创建其实例,因为您无法指定构造函数。因此,它不能具有实例状态,尽管接口字段可以定义常量,这些常量隐含为静态和final。

您不能在接口中指定方法体或初始化块,不过自Java 8以来,您可以指定带有方法体的默认方法。此功能旨在允许添加新方法到现有接口中而不必更新所有实现。但是,在执行此类方法之前,仍然需要创建实现接口的实例。

附注:请注意,您可以使用匿名内部类实现接口:

interface Foo {
    String bar();
}

class FooBar {
    Foo anonymous = new Foo() {
         public String bar() {
             return "The Laundromat Café";
    };
}

为了编译,您必须提供匿名内部类的完整实现。

new Foo()使用默认构造函数初始化匿名内部类。


57
final字段不一定是常量,只有对于原始类型才有保证。通常情况下,final关键字仅表示内存位置不会改变。 - Pops
8
我并没有说final字段就是常量,只是常量是final字段。需要注意的是,在接口中可以放置非原始类型的静态final字段。即使该字段的内容可能会更改,对其的引用仍然是恒定的。 - Adriaan Koster
1
@AdriaanKoster,你确切地说了最终字段是常量:“只允许常量不强制执行任何状态”-这句话意味着所有最终字段都是常量。你可能会尝试进一步争论你使用的词语,但显然你的陈述至少是误导性的。 - Tomáš Zato
2
这可能是我的智力正在衰退,但在看了六年之后,这个回答仍然是我得分最高的回答,我仍然不理解这些评论。请建议一种不同的措辞,因为我看不出有任何问题。 - Adriaan Koster
也许Java设计者的初衷是使接口无状态,但他们失败了,因为实例字段可以是可修改的类。他们选择强制将实例字段设置为static final,而不是承认他们失败了。在Java中,这是最接近真正(真正的C/C++)const的方法。不幸的是,这是隐式的,可能会导致非专家的困惑。(我刚意识到它们是static,因为我观察到了意外的行为。我从这个答案中学到它们只是final。) - not-a-user

31

final 的原因

如果字段没有被定义为 final,它们的值可以由任何实现更改,然后它们将成为实现的一部分。接口是一个纯粹的规范,没有任何实现。

static 的原因

如果它们是 static,那么它们属于接口,而不属于对象或对象的运行时类型。


18
这里有几个点被忽略了:
仅仅因为接口中的字段隐式地被声明为静态不可变,并不意味着它们必须是编译时常量,甚至是不可变的。你可以定义例如:
interface I {
  String TOKEN = SomeOtherClass.heavyComputation();
  JButton BAD_IDEA = new JButton("hello");
}
(请注意,在注解定义内部执行此操作可能会混淆javac,这与上述实际编译为静态初始化程序有关。)
(此限制的原因更多是出于风格而非技术上的考虑,很多人都希望它能够放宽一些。)

9

这些字段必须是静态的,因为它们不能像方法一样是抽象的。由于它们不能是抽象的,实现者将无法逻辑地提供字段的不同实现。

我认为这些字段必须是final的,因为这些字段可能被许多不同的实现程序访问,允许它们是可变的可能会有问题(例如同步)。此外,这也避免了它被重新实现(隐藏)。

以上是我的想法。


NawMan,你关于“字段必须是静态的...”的解释并不太有意义。但是你对“字段必须是final的...”非常正确。 - peakit
1
我不认为他对字段必须是final的原因是正确的。允许不同的实现者更改字段并不会有问题,否则继承将会有问题。 正如Adriaan所说,字段必须是final,因为接口是无状态的。具有状态的接口基本上应该是一个抽象类。 - Axelle Ziegler
如果您有一个public static字段不是final,findbugs会抱怨(理所当然!)。 - Tom Hawtin - tackline

2
我认为要求字段必须是final过于限制了,这是Java语言设计者的一个错误。在处理树等操作时,有时需要在实现中设置接口类型对象所需的常量才能执行操作。在实现类上选择代码路径是一种不太好的解决方法。我使用的解决办法是定义一个接口函数,并通过返回字面值来实现它。
public interface iMine {
    String __ImplementationConstant();
    ...
}

public class AClass implements iMine {
    public String __ImplementationConstant(){
        return "AClass value for the Implementation Constant";
    }
    ...
}

public class BClass implements iMine {
    public String __ImplementationConstant(){
        return "BClass value for the Implementation Constant";
    }
    ...
}

然而,使用以下语法会更简单、更清晰、更不容易出现异常实现:
public interface iMine {
    String __ImplementationConstant;
    ...
}

public class AClass implements iMine {
    public static String __ImplementationConstant =
        "AClass value for the Implementation Constant";
    ...
}

public class BClass implements iMine {
    public static String __ImplementationConstant =
        "BClass value for the Implementation Constant";
    ...
}

你似乎更多地抱怨字段是静态的而不是final的。 - Daniel Yankowsky

0

规范、合同... 对于字段访问的机器指令使用对象地址加上字段偏移量。由于类可以实现多个接口,因此无法使非最终接口字段在扩展此接口的所有类中具有相同的偏移量。因此必须实现不同的字段访问机制:两个内存访问(获取字段偏移量、获取字段值)而不是一个,同时还要维护一种虚拟字段表(类似于虚拟方法表)。我猜他们只是不想为可以通过现有功能轻松模拟的功能(方法)增加JVM的复杂性。

在Scala中,我们可以在接口中拥有字段,尽管在内部它们是按照我上面解释的方式实现的(作为方法)。


-2

静态:

在 Java 中,任何被声明为 static 的变量或方法都可以作为 Classname.variablenameClassname.methodname 直接调用。不必通过对象名称来调用它。

在接口中,无法声明对象,而 static 可以使得仅通过类名就可以调用变量,无需使用对象名称。

最终:

它有助于保持变量的常量值,因为它不能在其子类中被重写。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接