【发布时间】:2026-01-14 06:50:01
【问题描述】:
我正在学习Java,刚刚发现Interface可以有字段,它们是public static和final。到目前为止,我还没有看到任何这样的例子。这些接口常量有哪些用例,我可以在 Java 标准库中看到一些吗?
【问题讨论】:
我正在学习Java,刚刚发现Interface可以有字段,它们是public static和final。到目前为止,我还没有看到任何这样的例子。这些接口常量有哪些用例,我可以在 Java 标准库中看到一些吗?
【问题讨论】:
将静态成员放入接口(并实现该接口)是一种不好的做法,甚至还有一个名称,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);
}
}
【讨论】:
"常量接口模式是接口使用不当"
无论是谁编造了这个假设,无论他/她是一位大师,都是基于继续有效实施坏习惯和做法的需要而编造出来的。该假设是基于提升不良软件设计习惯的有效性。
我在这里写了一篇反驳该假设的回复:What is the best way to implement constants in Java?解释了这个假设的毫无根据。
10 年来,这个问题一直悬而未决,直到在我发布了反驳这个假设的理由后 2 小时内关闭,从而暴露了那些深信这个被误导的假设的人不愿意辩论。
这些是我反对假设的观点
支持这一假设的基础是需要方法和限制性规则来应对不良软件习惯和方法的影响。
观点的支持者“接口模式不变是对接口的一种糟糕使用”除了需要应对这些坏习惯的影响和实践。
解决根本问题。
那么为什么不充分利用和利用 Java 语言结构的每个语言特性来方便您自己呢。不需要夹克。为什么要制定规则来阻止您无效的生活方式来歧视和指责更有效的生活方式?
是信息组织。在设计或补充流程解决方案之前,应首先了解中介流程的信息以及该信息的行为以及所谓的业务规则。这种信息组织方法在几十年前被称为数据规范化。
那么只有解决方案的工程是可能的,因为将解决方案组件的粒度和模块化与信息组件的粒度和模块化保持一致是最佳策略。
组织信息存在两三个重大障碍。
缺乏对数据模型“规范化”需求的认识。
EF Codd 关于数据规范化的陈述存在缺陷、缺陷和模棱两可。
伪装成敏捷工程的最新时尚是一种错误观念,即不应提前计划和调整模块的组织,因为您可以随时进行重构。以不受未来发现阻碍的重构和持续变化为借口。然后,通过使用会计技巧来延迟利润和资产化,过程信息行为的基本发现,因此现在认为不需要基本知识及其处理。
不要仅仅因为你喜欢你的临时即兴编程习惯而制定规则或发布任何反对它的教令。
不要因为有些人不知道如何处理枪支或容易滥用枪支而禁止拥有枪支。
如果您制定的规则是为无法专业编码的编程新手设计的,并且您将自己视为其中之一,那么请这么说 - 不要将您的法特瓦声明为适用于正确规范化的数据模型。
我不在乎开国元勋对美国宪法的初衷是什么。我不在乎那些不成文的未编码意图。我只关心成文宪法中的文字内容,以及我如何利用它们来促进社会的有效运转。
我只关心 Java 语言/平台规范允许我做什么,我打算充分利用它们,为我提供一种媒介来有效地表达我的软件解决方案。不需要夹克。
它需要编写额外的代码来将参数映射到值。事实上,Java 的创始人在没有您编写映射代码演示的情况下没有提供参数值映射这一事实与 Java 语言的无意使用一样。
特别是由于不鼓励您对参数进行规范化和组件化,因此会产生错误的印象,即混合到 Enum 包中的参数属于同一维度。
不要忘记这一点。如果您设计并规范化了您的数据模型,并且它们包含常量,那么这些常量就是合同。如果你没有规范化你的数据模型,那么你应该遵守关于如何练习限制性编码以应对这种坏习惯的教令。
因此,接口是实现常量契约的完美方式。
是的。任何人都可能在不经意间无意中实现了任何接口。没有什么可以阻止这些不经意的程序员。
不要制定限制性法令来保护假定的不良做法,这些做法会导致未签约/杂散参数泄漏到 API 中。解决根本问题,而不是把责任归咎于接口常量。
一个正常运作且有效的程序员并不能证明她可以在水下呆多久,她可以在酷热或潮湿的雷暴中走多远。她将使用一种高效的工具,例如汽车或公共汽车,或者至少是一辆自行车,让她每天上班 10 英里。
不要仅仅因为你对无 IDE 编程有一种深奥的苦行僧痴迷,就对其他程序员施加限制。
OSGI 就是这样一个框架。禁止接口常量的法令也是如此。
接口常量是一种有效且高效的方式,可以将精心设计和规范化的数据模型组件放入 Contract。
嵌套在类文件中的适当命名的私有接口中的接口常量也是对所有私有常量进行分组而不是将它们分散在整个文件中的好习惯。
【讨论】:
我现在多次遇到这个老问题,但接受的答案仍然让我感到困惑。经过一番思考,我觉得这个问题可以进一步澄清。
比较一下:
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 之类的常量接口时,任何人都不应该实现它。 (虽然技术上可行,但这是这里唯一的问题)
标准库无法承受任何可能的设计误用,因此您将看不到任何设计。
不过,对于普通开发者的日常项目,使用常量接口要容易很多,因为你不用担心static、final、empty constructor等,而且不会导致任何不良设计问题。我能想到的唯一缺点是它仍然有“接口”的名称,但仅此而已。
最后,我认为每个人都只是引用书籍,并为他们的立场提供意见和理由。我也不例外。也许这个决定仍然取决于每个项目的开发人员。如果您感觉舒适,请继续使用它。我们能做的最好的事情就是使其在整个项目中保持一致。
【讨论】:
public也可以省略,因为它是一个接口,使其简单double PI = 3.14159;Constants.PI的使用不需要使用它的类来实现Constants接口!我认为界面方法在使用方面要干净得多,恕我直言
Joshua Bloch,“有效的 Java - 编程语言指南”:
常量接口模式是对接口的不良使用。那一个 类在内部使用一些常量是一个实现细节。 实现一个常量接口会导致这个实现细节 泄漏到类的导出 API 中。对它没有任何影响 一个类的用户认为该类实现了一个常量接口。在 事实上,它甚至可能使他们感到困惑。更糟糕的是,它代表了一种承诺:如果 在未来的版本中,该类被修改,使其不再需要 要使用常量,它仍然必须实现接口以确保 二进制兼容性。如果一个非final类实现了一个常量 接口,它的所有子类的命名空间都会被污染 通过接口中的常量。
【讨论】:
如果您有将在实现接口的类中使用的公共常量,它们会很有用。
这是一个例子: http://www.javapractices.com/topic/TopicAction.do?Id=32
但请注意,推荐的做法是在接口中使用静态导入而不是常量。这是一个参考:http://www.javapractices.com/topic/TopicAction.do?Id=195
【讨论】:
有些答案非常合理。
但我对这个问题有一些想法。 (可能有误)
在我看来,接口中的字段不应该是整个项目的常量,它们只是接口的手段,接口扩展它以及实现这些接口或与它们有密切关系的类。它们应该在一定范围内而不是全局范围内使用。
【讨论】:
关于界面的两点:
接口描述了实现它的对象的子集。 (这是直觉)
一个接口描述了通用常量,然后是实现它的对象。
所以我认为如果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中定义的常量,否则他们必须选择一个更糟糕的名字,因为好的PI和GOLDEN_RATIO已经被占用了!Drawable对象符合Drawable中定义的具体PI和GOLDEN_RATIO,可以有不Drawable的对象,具有不同的pi精度和黄金比例。【讨论】:
我遇到了这个问题,并认为我会添加一些未提及的内容。总的来说,我同意 Pascal 的回答 here。但是,我不认为接口上的常量“总是”是一种反模式。
例如,如果您定义的常量是 那个接口,我认为接口是常量的好地方。在某些情况下,在不将合约暴露给实现的用户的情况下私下验证您的参数是不合适的。如果没有面向公众的合约,用户只能猜测您使用什么进行验证,而无法反编译类并阅读您的代码。
因此,如果您实现了一个接口,并且该接口具有用于确保合同的常量(例如整数范围),那么您的类的用户可以通过检查常量来确保他们正确使用了接口实例在界面本身。如果常量对您的实现或您的实现是私有的,这将是不可能的 是包私有的还是什么的。
【讨论】:
javax.swing.SwingConstants 接口是一个示例,它获取了在 swing 类中使用的静态字段。这使您可以轻松地使用类似的东西
this.add(LINE_START, swingcomponent);this.add(this.LINE_START, swingcomponent); 或 this.add(SwingComponents.LINE_START, swingcomponent);但是这个接口没有方法...
【讨论】:
在一种情况下,接口常量优于静态最终字段:枚举。接口常量可以在枚举常量声明中使用,使得它们也可以作为附加到枚举的离散值使用,而无需任何额外的规范。比如这个界面:
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() {
...
}
这样的字符串常量可以很容易地映射回与之关联的枚举常量,并且每个常量只声明一次。还有其他方法可以实现类似的结果,但没有一种方法如此简洁,并且都需要至少两个并行声明。
【讨论】:
在处理类之间的共享常量时,我使用接口常量。
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);
【讨论】:
字段应该在接口中声明,以便它们更容易 共享和引用,无需引入额外的耦合。
【讨论】: