接口常量有啥用?

Posted

技术标签:

【中文标题】接口常量有啥用?【英文标题】:What is the use of interface constants?接口常量有什么用? 【发布时间】:2011-02-09 05:12:12 【问题描述】:

我正在学习Java,刚刚发现Interface可以有字段,它们是public static和final。到目前为止,我还没有看到任何这样的例子。这些接口常量有哪些用例,我可以在 Java 标准库中看到一些吗?

【问题讨论】:

【参考方案1】:

将静态成员放入接口(并实现该接口)是一种不好的做法,甚至还有一个名称,Constant Interface Antipattern,请参阅@987654321 @,第 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;

并且要访问常量而不必完全限定它们(即不必在它们前面加上类名),请使用static import(从 Java 5 开始):

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

public class Calculations 

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

【讨论】:

好的,但是如果你有一个不仅用于常量定义的接口怎么办。所以我有一些由许多类实现的接口,它包含方法声明,但我也想在那里添加一些常见的值,例如大小。在这种情况下,它真的很糟糕吗?一般来说,我同意仅为常量创建接口是一种反模式。 这并不是一件坏事,只是因为有人在书中这么说。只要您不实现该接口来访问这些常量,就可以。 No No No。Effective Java 中的那句话是关于别的东西的!仅创建常量接口是保存这些常量的类的更好选择。有效的 java 说:“一个类在内部使用一些常量是一个实现细节”。但这里不是这样。我们正在谈论“全局”常量。谁会想要实现一个没有方法声明的接口? 我同意 ACV。如果常量接口不是导出的 API 模块的一部分,或者没有实现,我看不出问题。使用 const final 类是丑陋的:你需要一个私有的构造函数,因为它没有用处而使代码混乱。 这个答案没有正确地呈现“Effective Java”一书中的要点,因为它通过将要点放在括号中来“降级”要点:“(并实现该接口)”。相反,它强调接口中的常量点(除非您实现这样的接口,否则没问题)。这通常不是一个糟糕的答案,因为如果您仔细阅读引用的片段,那么您会看到“Effective Java”的作者最初的意思。但我发现它具有误导性。在我看来,“和实现该接口”部分应该用粗体来强调它。【参考方案2】:

"常量接口模式是接口使用不当"

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

我在这里写了一篇反驳该假设的回复:What is the best way to implement constants in Java?解释了这个假设的毫无根据。

10 年来,这个问题一直悬而未决,直到在我发布了反驳这个假设的理由后 2 小时内关闭,从而暴露了那些深信这个被误导的假设的人不愿意辩论。

这些是我反对假设的观点

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

观点的支持者“接口模式不变是对接口的一种糟糕使用”除了需要应对这些坏习惯的影响和实践。

解决根本问题。

那么为什么不充分利用和利用 Java 语言结构的每个语言特性来方便您自己呢。不需要夹克。为什么要制定规则来阻止您无效的生活方式来歧视和指责更有效的生活方式?

基本问题

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

那么只有解决方案的工程是可能的,因为将解决方案组件的粒度和模块化与信息组件的粒度和模块化保持一致是最佳策略。

组织信息存在两三个重大障碍。

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

    EF Codd 关于数据规范化的陈述存在缺陷、缺陷和模棱两可。

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

使用接口常量是很好的做法。

不要仅仅因为你喜欢你的临时即兴编程习惯而制定规则或发布任何反对它的教令。

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

如果您制定的规则是为无法专业编码的编程新手设计的,并且您将自己视为其中之一,那么请这么说 - 不要将您的法特瓦声明为适用于正确规范化的数据模型。

一个愚蠢的推理 - Java 语言的笨蛋不打算以这种方式使用接口?

我不在乎开国元勋对美国宪法的初衷是什么。我不在乎那些不成文的未编码意图。我只关心成文宪法中的文字内容,以及我如何利用它们来促进社会的有效运转。

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

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

它需要编写额外的代码来将参数映射到值。事实上,Java 的创始人在没有您编写映射代码演示的情况下没有提供参数值映射这一事实与 Java 语言的无意使用一样。

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

常量是 API 契约

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

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

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

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

设计和规范您的数据模型以防止泄漏

不要制定限制性法令来保护假定的不良做法,这些做法会导致未签约/杂散参数泄漏到 API 中。解决根本问题,而不是把责任归咎于接口常量。

不使用 IDE 是不好的做法

一个正常运作且有效的程序员并不能证明她可以在水下呆多久,她可以在酷热或潮湿的雷暴中走多远。她将使用一种高效的工具,例如汽车或公共汽车,或者至少是一辆自行车,让她每天上班 10 英里。

不要仅仅因为你对无 IDE 编程有一种深奥的苦行僧痴迷,就对其他程序员施加限制。

一些框架旨在帮助程序员继续有效地养成坏习惯。

OSGI 就是这样一个框架。禁止接口常量的法令也是如此。

因此结论性的答案...

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

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

【讨论】:

你的所有观点都可以在没有开玩笑、讽刺和情绪化的情况下表达出来。 *** 不是博客平台。 "它需要编写额外的代码来将参数映射到值。事实上,Java 的创始人在没有您编写映射代码的情况下没有提供参数-值映射的事实表明,枚举常量就像意外使用Java 语言。”如果不编写额外的代码,您将如何获得参数值映射? 使用接口常量。完全正确。【参考方案3】:

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

为什么要使用接口常量?

比较一下:

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 的回答强调错误,这是我的版本:

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

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

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

在标准库中不会发生

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

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

无休止的辩论

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

【讨论】:

“将静态成员放入接口(并实现该接口)是一种不好的做法。”不,不是 修饰符public也可以省略,因为它是一个接口,使其简单double PI = 3.14159;Constants.PI的使用不需要使用它的类来实现Constants接口!我认为界面方法在使用方面要干净得多,恕我直言 @krozaine 已更新。感谢您的提醒。任何有疑问的人的参考:“接口中定义的所有常量值都是隐式公共的、静态的和最终的。” - docs.oracle.com/javase/tutorial/java/IandI/interfaceDef.html【参考方案4】:

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

常量接口模式是对接口的不良使用。那一个 类在内部使用一些常量是一个实现细节。 实现一个常量接口会导致这个实现细节 泄漏到类的导出 API 中。对它没有任何影响 一个类的用户认为该类实现了一个常量接口。在 事实上,它甚至可能使他们感到困惑。更糟糕的是,它代表了一种承诺:如果 在未来的版本中,该类被修改,使其不再需要 要使用常量,它仍然必须实现接口以确保 二进制兼容性。如果一个非final类实现了一个常量 接口,它的所有子类的命名空间都会被污染 通过接口中的常量。

【讨论】:

您实际上没有添加任何尚未提及的价值。【参考方案5】:

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

这是一个例子: http://www.javapractices.com/topic/TopicAction.do?Id=32

但请注意,推荐的做法是在接口中使用静态导入而不是常量。这是一个参考:http://www.javapractices.com/topic/TopicAction.do?Id=195

【讨论】:

【参考方案6】:

有些答案非常合理。

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

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

【讨论】:

它们确实应该在那些实现类中使用。【参考方案7】:

关于界面的两点:

接口描述了实现它的对象子集。 (这是直觉)

一个接口描述了通用常量,然后是实现它的对象

这些通用常量旨在让客户知道以了解有关这些对象的更多信息。 所以 Constants Interface 对于定义 全局常量 确实是违反直觉的,因为 interface 用于描述 一些对象,而不是 所有对象 / 无对象(考虑全局的含义)。

所以我认为如果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对象符合Drawable中定义的具体PIGOLDEN_RATIO,可以有不Drawable的对象,具有不同的pi精度和黄金比例。

【讨论】:

是我误解了你的回答,还是你误解了Interface的用途?接口不是子集。接口是一个合约,订阅该合约的任何一方都必须提供该合约指定的交互。接口的唯一有效用途是使用它来声明/履行合同。 @BlessedGeek 你履行了一份合同,那么合同所描述的能力就是你能做的事情的一个子集。【参考方案8】:

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

例如,如果您定义的常量是 那个接口,我认为接口是常量的好地方。在某些情况下,在不将合约暴露给实现的用户的情况下私​​下验证您的参数是不合适的。如果没有面向公众的合约,用户只能猜测您使用什么进行验证,而无法反编译类并阅读您的代码。

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

【讨论】:

你所描述的接口和常量的问题,你可以向接口添加一个常量,但是没有强制你的 API 的使用者实际使用该常量的绑定。 @Daniel:我赞成您的评论,但现在我对您的问题有一个很好的解释:“没有强制您的 API 的使用者实际使用该常量的绑定”但现在客户端不能不再使用 CONSTANT_NAME,因为它们已经被使用过,这对我来说是个好兆头! 所以常量名称在你的类中不可用,但假设我要为常量命名完全相同的东西。使用接口来表示常量仍然是一种谬误,接口是 API 的契约。它绝不应该用于表示常量值。【参考方案9】:

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

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

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

【讨论】:

【参考方案10】:

在一种情况下,接口常量优于静态最终字段:枚举。接口常量可以在枚举常量声明中使用,使得它们也可以作为附加到枚举的离散值使用,而无需任何额外的规范。比如这个界面:

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() 
    ...

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

【讨论】:

【参考方案11】:

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

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,并且它们的构造函数是私有的(如果你真的只想要一袋常量)。另外,您避免人们扩展您的课程。你不能避免人们实现你的界面吗? 为什么在课堂上做你可以在界面上做的事情?如何防止人们无意中实现任何接口是个问题。你为什么选择接口常量?【参考方案12】:

字段应该在接口中声明,以便它们更容易 共享和引用,无需引入额外的耦合。

来源:Java Developer Tools Coding Style

【讨论】:

可能在 java6 之前。但这在今天几乎无关紧要。 链接已损坏。 Codepro 已不复存在。

以上是关于接口常量有啥用?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中,实型常量是啥意思?啥概念?有啥用?

安卓开发是用的那几个action常量有啥用啊?

在java语言中,有时候变量常量声明时要加一个“static”,有的时候不加。问一下这个词有啥用

Vue 中的 .sync 修饰符有啥用

java中的StringBuilder有啥用?啥时候用StringBuilder?

java的enum到底有啥用?