接口常量的用途是什么?

我正在学习 Java,刚刚发现接口可以有字段,这是公共静态和最终。到目前为止,我还没有看到任何这样的例子。这些接口常量的一些用例是什么,我可以在 Java 标准库中看到一些吗?

128404 次浏览

如果您有将在实现接口的类中使用的公共常量,那么它们将非常有用。

这里有一个例子: Http://www.javapractices.com/topic/topicaction.do?id=32

但是请注意,推荐的做法是在接口中使用静态导入而不是常量

javax.swing.SwingConstants接口是一个例子,它获得了在秋千类中使用的静态字段。这使您可以轻松地使用

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

但是这个接口没有方法..。

将静态成员放入接口(并实现该接口)是 坏习惯,甚至还有一个名称 常量接口反模式,参见 有效的爪哇,第17项:

常量接口模式很少使用接口 。类在内部使用某些常量是一个实现细节。实现常量接口会导致该实现细节泄漏到类的导出 API 中。类实现常量接口对于类的用户来说是无关紧要的。事实上,这甚至会让他们感到困惑。更糟糕的是,它代表了一种承诺: 如果在将来的发行版中,类被修改为不再需要使用常量,那么它仍然必须实现接口以确保二进制兼容性。如果一个非 final 类实现了一个常量接口,那么它的所有子类的名称空间都会受到接口中常量的污染。

Java 平台库中有几个常量接口,比如 java.io.ObjectStreamConstants。这些接口应被视为异常和 不应该被模仿。

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

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);
}
}

Joshua Bloch,《有效的 Java 编程语言指南》 :

常量接口模式对接口的使用不足 类在内部使用一些常数是一个实现细节。 实现常量接口将导致此实现的详细信息 泄漏到类的导出 API 中 类实现常量接口的类的用户 更糟糕的是,它代表着一种承诺: 如果 在将来的版本中,这个类会被修改,这样它就不再需要 要使用常量,它仍然必须实现接口以确保 二进制兼容性。如果一个非最终类实现一个常量 接口,则其所有子类的命名空间都将受到污染 通过接口中的常量。

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

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);

字段应该在接口中声明,以便它们更容易 共享,并且可以在不引入额外耦合的情况下引用。

资料来源: Java 开发工具编码风格

我无意中发现了这个问题,我想我应该补充一些没有被提及的东西。总的来说,我同意帕斯卡的答案 给你。但是,我不认为接口上的常量“总是”反模式。

例如,如果您正在定义的常量是 那个接口,我认为这个接口是一个很好的常量位置。在某些情况下,私下验证参数而不向实现的用户公开契约是不合适的。如果没有面向公共的契约,用户只能猜测您的验证使用了什么,而不能反编译类并读取代码。

因此,如果你实现了一个接口,并且该接口具有用于确保契约的常量(例如整数范围) ,那么类的用户可以通过检查接口本身的常量来确保他们正确地使用了接口实例。如果这些常量对于您的实现或实现是私有的,那么这是不可能的 是私人包裹什么的。

常量接口模式对接口的使用很差

无论是谁捏造了这个假设,无论他/她可能是一个大师,都是基于继续有效地实施坏习惯和实践的需要而捏造出来的。该假设是基于提升不良软件设计习惯的有效性。

我在这里写了一篇反驳这个假设的文章: 在 Java 中实现常量的最佳方法是什么?解释了这个假设的毫无根据。

10年来,这个问题一直没有解决,直到在我发布了证明这个假设不合理的理由后的两个小时内,这个问题才被关闭,从而暴露了那些坚持这个错误假设的人的不情愿的辩论。

这些是我反对这个假设的观点

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

  • 这种情绪的支持者“ 不变的接口模式是对接口的糟糕使用”不能提供任何理由,除了那些需要应对这些坏习惯和做法的影响所造成的理由。

  • 解决根本问题。

  • 那么,为什么不充分利用和利用 Java 语言结构的每一个语言特性,以达到您自己的方便呢。不需要外套。为什么要制定规则来限制你无效的生活方式,从而歧视和控告更有效的生活方式呢?

最根本的问题

资讯组织。在对流程进行设计或补充解决方案之前,首先应该理解中介流程的信息和该信息的行为,以及所谓的业务规则。这种信息组织方法在几十年前被称为数据规范化。

然后只有解决方案的工程是可能的,因为使解决方案组件的粒度和模块性与信息组件的粒度和模块性相一致是最佳策略。

在组织信息方面有两到三个重要的障碍。

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

  2. EF Codd 关于数据规范化的陈述是有缺陷的、有缺陷的和模棱两可的。

  3. 伪装成敏捷工程的最新时尚是一种错误的观念,即不应该提前计划和调整模块的组织,因为您可以随时进行重构。重构和持续的更改不受未来发现的阻碍被用作借口。过程信息行为的基本发现,然后,通过使用会计技巧延迟利润和资产化,因此必要的知识和它们的处理被认为是现在不需要的。

使用接口常量是一个很好的实践。

不要仅仅因为你喜欢你的特别的打了就跑的编程习惯而制定规则或发布任何反对它的法特瓦。

不要禁止拥有枪支,理由是有人不知道如何处理枪支或有滥用枪支的倾向。

如果你炮制的规则是针对那些不能专业地编写代码的编程新手,并且你认为自己也是其中之一,那么就这么说——不要声明你的教令适用于正确规范化的数据模型。

一个愚蠢的推理—— Java 语言的创始人并不打算这样使用接口?

我不在乎开国元勋们对美国宪法的初衷是什么。我不在乎不成文的不成文的意图。我只关心成文宪法中文字上的规定,以及我如何利用它们促进社会的有效运作。

我只关心 Java 语言/平台规范允许我做什么,我打算充分利用它们,为我提供一个媒介,以高效和有效地表达我的软件解决方案。不需要外套。

使用 Enum 常量实际上是一种可怕的做法。

它需要编写额外的代码来将参数映射到值。事实上,Java 的创始人没有提供参数值映射,除非你写了映射代码演示 Enum 常量,这也是 Java 语言的意外使用。

特别是因为不鼓励对参数进行规范化和组件化,所以会产生一种错误的印象,即混合到 Enum 包中的参数属于同一个维度。

常量是 API 契约

别忘了。如果您设计并规范化了数据模型,并且它们包含常量,那么这些常量就是契约。如果您没有规范化您的数据模型,那么您应该遵守关于如何实践限制性编码来应对这个坏习惯的教令。

因此,接口是实现常量契约的一种完美方式。

一个奇怪的假设——如果无意中实现了接口会怎样。

是的。任何人都可能在不经意间实现任何接口。没有什么可以阻挡这些不经意的程序员。

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

不要颁布限制性法令来保护那些可能导致未收缩/杂散参数泄漏到 API 中的不良做法。解决根本问题,而不是把责任归咎于接口常量。

不使用 IDE 是不好的做法

一个正常工作和有效的程序员不会在那里证明她能在水下呆多久,她能在酷热或潮湿的雷雨中走多远。她要使用一种高效的工具,如汽车或公共汽车或至少一辆自行车,每天带她10英里的工作。

不要仅仅因为你对没有 IDE 的编程有深奥的禁欲主义的痴迷就对程序员同事施加限制。

设计了一些框架来帮助程序员有效地继续练习坏习惯。

OSGI 就是这样一个框架,对接口常量的限制也是如此。

因此,最终的答案是..。

接口常量是将数据模型设计良好、规范化的组件放入契约中的一种有效方法。

嵌套在类文件中的适当命名的私有接口中的接口常量也是将所有私有常量分组而不是将它们散布在文件中的良好实践。

有些答案是非常合理的。

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

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

这个老问题我现在已经遇到好几次了,但是被接受的答案仍然让我困惑。经过深思熟虑,我认为这个问题可以进一步澄清。

为什么使用接口常量?

比较一下:

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;
}

用法相同,代码少多了。

训练不好吗?

我认为@帕斯卡尔•蒂文特的回答强调错误,以下是我的观点:

将静态成员放入接口(并实现那个接口)是一种糟糕的做法。

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

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

在标准库中不会发生这种情况

标准库无法承受任何可能的设计误用,因此您在那里不会看到任何误用。

然而 ,对于普通开发人员的日常项目,使用常量接口要容易得多,因为你不需要担心 staticfinalempty constructor等等,它不会导致任何糟糕的设计问题。我能想到的唯一缺点是它仍然有“接口”的名称,但仅此而已。

没完没了的辩论

最后,我想每个人都只是引用书中的内容,给出自己的观点和理由。我也不例外。也许这个决定仍然取决于每个项目的开发人员。如果你觉得舒服就用吧。我们能做的就是把它变成 在整个项目中始终如一

关于界面有两点:

  • 接口描述了实现它的 对象一个子集功能(这是直觉)

  • 接口描述 公共常数,然后是实现它的 物品

    • 这些常量 是有意的将被客户机知道,以便更多地了解这些对象。
    • 因此,常量接口在定义 全局常数时的确是违反直觉的,因为接口是用来描述 一些物品的,而不是 所有物品/没问题(考虑 全球性的的含义)。

所以我认为如果 常量接口不用于 全局常数,那么它是可以接受的:

  • 如果这些 公共常数有好的名字,它们将 推荐实现者使用它们(参见下面示例的第一点)
  • 如果一个类想要与那些遵循规范的类同步,只需要 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对象符合 Drawable中定义的特定 PIGOLDEN_RATIO,才能产生不同精度的 π 和黄金比值的 Drawable以外的对象。

在一个上下文中,接口常量优于静态 final 字段: 枚举。接口常量可以在枚举常量声明中使用,这种方式使它们也可以作为附加到枚举中的离散值使用,而不需要任何其他规范。例如,这个界面:

public interface Name {
String MANNY = "Manny";
String MOE = "Moe";
String JACK = "Jack";


String getName();
}

... 可以提供字符串常量,可以在枚举常量中使用,如下所示:

public enum PepBoys implements Name {
BOY1(MANNY),
BOY2(MOE),
BOY3(JACK);


private String name;


PepBoys(String name) {
this.name = name;
}


@Override
public String getName() {
return name;
}
}

注释属性的值必须是常量值,而且(具有讽刺意味的是)枚举的常量在此上下文中不符合“常量”的标准。但是,在接口中定义的字符串常量符合以下条件:

@MyAnnotation(PepBoys.MANNY)
public void annotatedMethod() {
...
}

这样的字符串常量可以很容易地映射回与它们关联的枚举常量,并且每个常量只声明一次。还有其他方法可以实现类似的结果,但没有一种方法如此简洁,而且所有方法都需要至少两个并行声明。

如果它小于64个常量,我会更喜欢 Enums。如果是一个较小的数字,我有时会使用 Interfaces,尽管我读到它是一个反模式。

我曾经开发过一个应用程序,它有数百个界面常量,对我来说那是代码的味道。我认为静态导入对于那个用例来说会更好,但是我猜测是因为我认为性能可能会更好。

此外,还有一篇名为: Java 常量接口的文章解释了“字段阴影”的问题,并展示了实现接口的类中的值如何被覆盖的代码,我认为这很有趣。他通过测试而不是通过观点提出了一个有效的案例,而这正是我过去所发现的。

字段或变量阴影定义(来自 什么是场地跟踪?) :

当我们在闭包中定义一个变量时,变量隐藏就会发生 使用与变量名相同的变量名进行调用 我们已经在外部范围内定义了。

因此,总结一下,作为一般规则,在接口上使用 Enums 或静态导入。