接口常量的用途是什么?


123

我正在学习Java,只是发现接口可以包含公共静态字段和最终字段。到目前为止,我还没有看到这些示例。这些接口常量有哪些用例,我可以在Java标准库中看到吗?

Answers:


190

将静态成员放入接口(并实现该接口)是一种不好的做法,甚至还有一个名称,即Constant Interface Antipattern,请参见Effective Java,第17项:

恒定接口模式是对接口的不良使用。类内部使用一些常量是一个实现细节。实现常量接口会导致此实现细节泄漏到类的导出API中。对类的用户而言,该类实现一个常量接口并不重要。实际上,它甚至可能使他们感到困惑。更糟糕的是,它表示一种承诺:如果在将来的版本中对该类进行了修改,使其不再需要使用常量,则它仍必须实现该接口以确保二进制兼容性。如果非最终类实现了常量接口,则其所有子类的名称空间都将受到接口中常量的污染。

Java平台库中有几个常量接口,例如java.io.ObjectStreamConstants。这些接口应被视为异常并且不应被仿真。

为了避免常量接口的某些缺陷(因为您不能阻止人们实现它),应该首选带有私有构造函数的适当类(例如,从Wikipedia借用的示例):

public final class Constants {

    private Constants() {
        // restrict instantiation
    }

    public static final double PI = 3.14159;
    public static final double PLANCK_CONSTANT = 6.62606896e-34;
}

要访问常量而不必完全限定常量(即不必在类名前添加常量),请使用静态导入(自Java 5起):

import static Constants.PLANCK_CONSTANT;
import static Constants.PI;

public class Calculations {

    public double getReducedPlanckConstant() {
        return PLANCK_CONSTANT / (2 * PI);
    }
}

12
好的,但是如果您有一个不仅用于常量定义的接口,该怎么办。所以我有一些由许多类实现的接口,其中包含方法声明,但我也想在其中添加一些常见的值,例如size。在这种情况下,它真的是不好的模式吗?通常,我同意仅为常量创建接口是一种反模式。
卢卡斯Rzeszotarski

11
这不是一件坏事,只是因为有人在书中这样说。只要您不实现该接口来访问这些常量,就可以了。
ACV

11
不,不,不。EffectiveJava中的那句话是关于别的东西!创建仅常量的接口是保存这些常量的类的更好的选择。有效的Java表示:“类内部使用一些常量是实现细节”。但事实并非如此。我们正在谈论“全局”常量。谁想要实现一个没有方法声明的接口?
ACV

6
我同意ACV。如果常量接口不是导出的API模块的一部分,或者未在任何地方实现,则看不到问题。使用const final类很丑陋:您需要一个私有的构造函数,因为它没有用,所以会使代码混乱。
劳伦斯

2
该答案不能正确呈现“有效Java”一书中的要点,因为它通过将要点放在方括号“(并实现该接口)”中来“降级”要点。相反,它强调了接口中常量的要点(除非实现了这样的接口,否则它是可以的)。这通常不是一个糟糕的答案,因为如果您仔细阅读引用的片段,那么您可以看到“ Effective Java”作者的初衷。但是我发现它具有误导性。我认为“和实现该接口”部分应使用黑体字强调。
KRM

17

恒定的接口模式是对接口的不良使用

不管这个假说是谁,无论他/她是谁,都可以基于有效地继续养成不良习惯和习惯的需要来假想。该假设是基于对不良软件设计习惯有效性的促进。

我在此处针对该假设写了一个反驳:在Java中实现常量的最佳方法是什么?解释这个假设的无根据。

十年来,这个问题一直悬而未决,直到我发表我的理由使这一假设变得合理之后的两个小时之内,这个问题才得以解决,从而使那些坚守这一被错误引导的假设的人们深深地感到不知所措。

这些是我针对假设表达的观点

  • 保持该假设的基础是需要方法和限制性规则来应对不良软件习惯和方法论的影响。

  • 支持“ 不变的接口模式是对接口的不良使用这一观点的拥护者,除了由于需要应付那些不良习惯和习惯所造成的原因外,无法提供任何其他原因。

  • 解决根本问题。

  • 然后,为什么不充分利用Java开发语言结构的每种语言功能,以方便您使用呢?无需夹克。为什么要制定规则来限制您无效的生活方式,以区别和赋予更有效的生活方式?

基本问题

是信息组织。在对流程进行工程设计或补充解决方案之前,应首先了解与流程相关的信息,并首先了解该信息的行为以及所谓的业务规则。这种信息组织方法在几十年前被称为数据标准化。

然后,仅解决方案的工程是可能的,因为将解决方案的组件的粒度和模块化与信息组件的粒度和模块化对齐是最佳策略。

组织信息有两个或三个主要障碍。

  1. 缺乏对数据模型“规范化”需求的认识。

  2. EF Codd关于数据标准化的陈述是有缺陷的,有缺陷的和模棱两可的。

  3. 伪装成敏捷工程的最新时尚是一种误导性的观念,即人们不应该计划和限制未来的模块组织,因为您可以随心所欲地进行重构。重构和持续变化不受将来发现的阻碍被用作借口。然后,通过使用会计技巧来延迟利润和资产配置,来实质性发现过程信息的行为,因此,现在认为不需要基本知识及其处理方法。

使用接口常量是一种好习惯。

不要仅仅因为您喜欢临时的即插即用编程习惯,就制定规则或对它发布任何废话。

不要因为某些人不知道如何操作枪支或容易滥用枪支而禁止枪支拥有。

如果您炮制的规则是为无法进行专业编程的新手编写的,并且您认为自己属于其中,那么请这样说-请勿声明您的Fatwa适用于正确归一化的数据模型。

愚蠢的推理-Java语言的比喻不打算使用这种接口吗?

我不在乎开国元勋对美国宪法的初衷。我不在乎那些未成文的未成文的意图。我只关心书面《宪法》中的文字内容,以及如何利用它们来实现社会的有效运转。

我只在乎Java语言/平台规范允许我做什么,我打算充分利用它们来为我提供一种有效且高效地表达我的软件解决方案的媒介。无需夹克。

枚举常量的使用实际上是可怕的做法。

它需要编写额外的代码以将参数映射到值。Java的创建者没有提供参数值映射的事实,而无需您编写映射代码就证明了Enum常量就是对Java语言的意外使用。

尤其是由于不鼓励您对参数进行规范化和组件化,因此会产生错误的印象,即混合到Enum包中的参数属于同一维。

常量是API合同

别忘了 如果您设计并规范化了数据模型,并且它们包含常量,那么这些常量就是合同。如果您没有规范化数据模型,那么您应该遵守关于如何练习限制性编码以应对这种不良习惯的说法。

因此,接口是实现Constants合同的理想方式。

一个奇怪的假设-如果无意间实现了接口该怎么办。

对。任何人都可能会无意间实现任何接口。没有什么可以阻止这种无心的程序员。

设计并规范化数据模型以防泄漏

不要放置限制性命令来保护假定的不良做法,这些不良做法会导致未约定/杂散参数泄漏到API中。解决根本问题,而不是怪罪于接口常数。

不使用IDE是一个坏习惯

没有一个正常运行且有效的程序员来证明她可以在水下呆多长时间,在炎热或潮湿的雷暴天气中可以走多远。她将使用汽车,公共汽车或至少一辆自行车等高效工具,每天花10英里去上班。

不要仅仅因为对IDE少的编程有一种深奥的禁欲主义而对其他程序员没有限制。

设计了两个框架,以帮助程序员继续有效地养成不良习惯。

OSGI是这样的框架。反对接口常量的法令也是如此。

因此,最终的答案...

接口常量是将Contract精心设计和规范化的数据模型组件放入Contract的一种有效方式。

嵌套在类文件中的适当命名的私有接口中的接口常量也是对所有私有常量进行分组而不是将它们分散在整个文件中的一种好习惯。


29
您的所有观点都可以在不开玩笑,讽刺和情感的情况下提出。Stackoverflow不是博客平台。
tkruse

1
“我不在乎开国元勋对美国宪法的初衷。我不在乎未成文的未成文的意图。我只在乎成文的《宪法》中有什么成文法则以及如何利用它们社会的有效运作。” -那不是很好的寓言吗?
Blessed Geek

“这需要编写额外的代码才能将参数映射到值。事实上,Java的创建者没有提供参数值映射就没有您写映射代码证明Enum常量同样是Java语言的意外使用。” 不编写额外代码,您将如何获得参数-值映射?
加布里埃尔·奥斯朗

使用接口常量。究竟。
祝福极客

9

我现在几次遇到这个老问题,但被接受的答案仍然使我感到困惑。经过多番思考,我认为这个问题可以进一步澄清。

为什么要使用接口常量?

只需比较一下:

public final class Constants {

    private Constants() {
        // restrict instantiation
    }

    public static final double PI = 3.14159;
    public static final double PLANCK_CONSTANT = 6.62606896e-34;
}

public interface Constants {

    double PI = 3.14159;
    double PLANCK_CONSTANT = 6.62606896e-34;
}

用法相同。更少的代码。

不好的做法?

我认为@Pascal Thivent的答案强调的错误,这是我的说法:

将静态成员放入接口(并实现该接口)是一种不好的做法。

有效Java中的引言假设常量接口由其他人实现,我认为这不会(也不会)发生。

当您创建名为的常量接口时Constants,任何人都不应实现它。(尽管在技术上可行,但这是这里唯一的问题)

它不会在标准库中发生

标准库不能承担任何可能的滥用设计,因此您不会在其中看到任何东西。

然而,对于普通开发人员的日常项目,使用常量接口是一个容易得多,因为你不必操心staticfinalempty constructor等,并不会造成任何不良的设计问题。我能想到的唯一缺点是它仍然具有“接口”的名称,仅此而已。

无休止的辩论

最后,我认为每个人都只是在引用书本,并为自己的立场提供意见和理由。我也不例外。也许决定仍然取决于每个项目的开发人员。如果您感到舒适,请继续使用它。我们能做的最好的事情就是在整个项目中保持一致


“将静态成员放入接口(并实现该接口)是一种不良做法。” 不,不是
下雨了

修饰符public也可以省略,因为它是一个接口,从而使其简单地double PI = 3.14159;使用Constants.PI不需要使用该修饰符的类来实现Constants接口!我认为接口方法在用法方面更加
简洁

@krozaine更新。感谢您的提醒。任何有疑问的人的参考:“接口中定义的所有常量值都是隐式的public,static和final。” - docs.oracle.com/javase/tutorial/java/IandI/interfaceDef.html
中村

6

Joshua Bloch,“有效的Java-编程语言指南”:

恒定接口模式是对接口的不良使用。类内部使用一些常量是一个实现细节。实现常量接口会导致此实现细节泄漏到类的导出API中。对类的用户而言,该类实现一个常量接口并不重要。实际上,它甚至可能使他们感到困惑。更糟糕的是,它表示一种承诺:如果在将来的版本中对该类进行了修改,使其不再需要使用常量,则它仍必须实现该接口以确保二进制兼容性。如果非最终类实现了常量接口,则其所有子类的名称空间都会受到接口中常量的污染。


您几乎没有添加任何尚未说过的有价值的东西。
Donal Fellows


2

有些答案很合理。

但是我对此问题有一些想法。(可能是错误的)

在我看来,接口中的字段不应是整个项目的常数,它们只是接口的手段,并且接口扩展了接口以及实现这些接口或与它们有密切关系的类。应该在一定范围内而不是全局范围内使用它们。


实际上,应该在这些实现类中使用它们。
下雨

2

关于界面的两点:

  • 接口描述实现它的对象可以做什么的子集。(这是直觉)

  • 接口描述公共常量,后跟实现该接口的对象

    • 客户端希望了解这些公共常数以了解有关这些对象的更多信息。
    • 因此,常量接口确实对于定义全局常量是违反直觉的,因为接口用于描述某些对象,而不是所有对象 / 没有对象(考虑global的含义)。

因此,我认为如果Constants Interface不用于全局常量,则可以接受:

  • 如果这些公共常数有好名字,他们会建议实现者使用它们(请参见下面示例的第一点)
  • 如果一个类想按照规范与那些类同步,就implements可以了(当然,在实现中使用这些公共常量)。

例:

interface Drawable {

    double GOLDEN_RATIO = 1.618033988;
    double PI = 3.141592653;
    ...

    // methods
    ...
}

public class Circle implements Drawable {
    ...
    public double getCircumference() {
        return 2 * PI * r;
    }
}

void usage() {

    Circle circle = new Circle(radius: 3.0);
    double maxRadius = 5.0;

    if ( circle.getCircumference() < 2 * Circle.PI * maxRadius ) {
        ...
    }

}

在此示例中:

  • 从中Circle implements Drawable,您立即知道它Circle可能符合中定义的常量Drawable,否则它们必须选择一个较差的名称,因为好的名称PIGOLDEN_RATIO已经采取的!
  • 只有这些Drawable对象符合中定义PIGOLDEN_RATIO定义的Drawable对象,才能Drawable存在pi和黄金分割率精度不同的对象。

是我误解了您的答案还是误解了Interface的目的?接口不得成为子集。接口是一个合同,任何订阅该合同的人都必须提供该合同指定的交互。接口的唯一有效用途是使用它来声明/履行合同。
Blessed Geek

@BlessedGeek如果您履行合同,那么合同描述的能力就是您可以做的事情的一部分。
下雨

1

javax.swing.SwingConstants接口是一个示例,该示例获取了在swing类之间使用的静态字段。这使您可以轻松使用类似

  • this.add(LINE_START, swingcomponent);
  • this.add(this.LINE_START, swingcomponent); 要么
  • this.add(SwingComponents.LINE_START, swingcomponent);

但是此接口没有方法...


1

我遇到了这个问题,以为我会添加未提及的内容。总的来说,我在这里同意Pascal的回答。但是,我不认为接口上的常量“总是”是反模式的。

例如,如果您要定义的常量是该接口的合同的一部分,那么我认为接口是常量的理想之选。在某些情况下,在不将合同暴露给实现的用户的情况下私​​下验证您的参数是不合适的。没有公开的合同,用户只能猜测您的验证内容,而无需反编译类并阅读您的代码。

因此,如果您实现一个接口并且该接口具有用于确保合同的常量(例如整数范围),则该类的用户可以通过检查接口中的常量来确保他们正确使用了接口实例他们自己。如果常量是您的实现私有的,或者您的实现是私有的,则这是不可能的。


1
接口和常量的描述方式存在问题,您可以向接口添加常量,但是没有绑定可以强制您的API使用者实际使用该常量。
丹尼尔(Daniel)

@Daniel:我赞成您的评论,但是现在我对您的问题有一个很好的解释:“没有绑定强制您的API使用者实际使用该常量”,但是现在客户端不再可以使用CONSTANT_NAME,因为它们已经用过,这对我来说是个好兆头!
下雨

因此,该常量名称在您的课程中不可用,但是假设我将使用完全相同的名称来命名该常量。使用接口表示常量仍然是谬论,接口是API的契约。决不能将其用于表示常数值。
丹尼尔(Daniel)

-1

在处理类之间的共享常量时,我​​使用接口常量。

public interface TestConstants
{
    String RootLevelConstant1 = "RootLevelConstant1";

    interface SubGroup1
    {
        String SubGroupConstant1 = "SubGroup1Constant1";
        String SubGroupConstant2 = "SubGroup1Constant2";
    }

    interface SubGroup2
    {
        String SubGroupConstant1 = "SubGroup2Constant1";
        String SubGroupConstant2 = "SubGroup2Constant2";
    }
}

分组是一项巨大的资产,尤其是在具有大量常量的情况下。

要使用,只需将它们链接在一起:

System.out.println(TestConstants.SubGroup1.SubGroupConstant1);
System.out.println(TestConstants.SubGroup2.SubGroupConstant1);
System.out.println(TestConstants.RootLevelConstant1);

我会同意的。它与具有公共静态final字段的公共抽象类基本相同,但用更少的词。您需要做的就是确保团队中的所有开发人员都遵循良好的做法,并且不要实现这些接口。
Yahor

1
这太可怕了,因为您可以对类进行完全相同的操作,以相同的方式访问,并将类定为final,并将它们的构造函数设为私有(如果您真的只想把一堆常量放在一起)。另外,您还可以避免其他人扩展您的课程。您无法避免人们实现您的界面,对吗?
加布里埃尔·奥西罗(GabrielOshiro)

1
为什么要在类中做什么您可以在界面中做什么?问题是如何防止人们无意间实现ANY接口。您为什么选择接口常数?
Blessed Geek,

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.