在Java接口中声明常量的目的是什么?

17

规范声明接口意在定义一个类能做什么并包含一组必需实现的方法,但同时接口也可以有常量。

在 Java 中允许这样做的目的是什么?

常量在接口中存在的意义是什么,它们如何在接口中使用?据我所知,它们只能作为方法的参数,但我并不认为这样做有很大的意义,因为接口并没有说明一个类将如何实现其方法。


它们可以被用作一种全局变量。 - DPM
5
好问题... 实际上,自从 Java 5 引入枚举类型以来,就我所知已经完全没有必要了。 - fge
例如,当接口实现了setFlags()方法并且这些标志(及其数值)由接口预定义(由接口文档)时。 - Michael Butscher
当你需要在子类中使用常量时,它们非常有用。例如,如果你有一个定义 HTTP 请求的 interface,那么为 GETPOST 等定义常量可能会很有用。话虽如此,请非常小心 常量接口反模式 - Boris the Spider
7个回答

11

常量也是接口的一部分。设计中使用常量值来避免魔数,即对实现有特定含义但似乎突然出现的数字。

有许多情况下,数字值会影响代码的行为。例如,考虑一个GUI按钮的接口。如何绘制这个按钮取决于具体的实现,但它是什么样的按钮是形成接口的合同的一部分:它是普通的推按钮、有图像还是复选框?这个行为可以使用常量修改,常用的方式是按位或值:int buttonType = PUSHBUTTON|IMAGEBUTTON


2
请注意(自Java 5以来),接口还可以声明一个“枚举”,这通常更适合于行为修改,因为它禁止任何未声明的选项并且是类型安全的。如果使用setButtonType(EnumSet<ButtonType> types)方法而不是常量方法setButtonType(int typesMask),即使是内部方法,也会更易读。 - Petr Janeček
2
枚举通常不适用于配置键。例如,当您想从属性文件中检索特殊属性时。这些通常是字符串,因此不使用枚举来绕过它们。 - Thomas Jungblut
1
@ThomasJungblut 如果是这样的话,你应该存储枚举的 name(),然后调用 YourEnum.valueOf(valFromProperties)。或者尝试一些 ordinal() 处理,但如果你不非常小心的话,那可能会更糟糕。无论如何,让我们不要在这里争论了,你和我都有各自的优缺点,每个人都可以决定走哪条路。 - Petr Janeček
如果我理解你的评论正确的话,那么按照惯例,在名称中是没有点的。MyEnum.HELLO.name() 返回的只是 "HELLO"。额外的字段(我会使用 int)用于将其“序列化”到属性中也是可行的,如果必须这样做,我绝对会采用这种方式,而不是使用位字段。话虽如此,两种方法都可以接受。 - Petr Janeček
1
当然,枚举中没有点。我指的是属性键。例如,请查看Hadoop或其他Apache项目的配置。 - Thomas Jungblut
显示剩余2条评论

2

想象一下一个使用图片或声音等资源的应用程序。您可以为您的资源定义一个公共接口。

interface MyResource {
    void load();
    void dispose();
    // ...
}

你的文件夹结构可能如下所示:

+ Root
|--+ Resources
   |--+ Images
   |--+ Sounds
   |--+ Data

你知道所有资源都将位于Root/Resources/下。这一信息不仅可以被你的资源所知,还可以被其他应用程序组件所知。
考虑到这一点,你现在的界面变成了:
interface MyResource {
    public static final String RESOURCE_ROOT_PATH = "Root/Resources/";

    void load();
    void dispose();
    // ...
}

您的具体实现(例如图像)可能会根据所有资源的公共路径定义自己的根路径。

class MyImage implements MyResource {
    public static final String IMAGE_ROOT_PATH =
            MyResource.RESOURCE_ROOT_PATH + "Images/";

        ...
}

或者,您可以查看像javax.swing.SwingConstants这样的接口,用于在实现之间共享某个功能的常量。

但是,就这种情况而言,我现在更愿意使用enum


2

这些常量可在接口外使用。

例如,它们可以被实现接口的类所使用。如果需要在实现类之间使用单个定义,则这可能非常有用。

举例来说,考虑一个定义了一些按位或样式常量的接口,这些常量可以在所有子类中一致地使用。只需要单个定义,只需要学习一组。


我知道这是可能的,但我不明白它的目的是什么。毕竟,这有点像通过接口来实现。 - Alex
一个常量值是数据,与实现分离。这里举个例子。 - Andy Thomas

1
让我们通过一个例子来解释:
public interface People {
    /**
     * Gets the population for the given type
     */
    public Population getPopulationForType(int populationType);
}

你是否觉得上述合同易于理解?还是下面的更好一些?
public interface People {
    int BLACK_POPULATION_TYPE = 0;
    int ASIAN_POPULATION_TYPE = 1;
    int WHITE_POPULATION_TYPE = 2;

    /**
     * Gets the population for the given type
     */
    public Population getPopulationForType(int populationType);
}

你可以有一个FrenchPeople、AmericanPeople和ChinesePeople的实现,但所有这些实现都应该返回三种可能类型人口的总数。

3
这不是一个好的示例,使用“枚举”会更加合适。 - Boris the Spider
枚举类型在Java 5中被引入。接口在Java 1.0中被引入。可以想象类似的用例,其中枚举可能不太合适。例如,您可以使用位掩码。 - JB Nizet
当然,在Java 5之前,接口常量对于这种用法是很有意义的。如果我们谈论Java 5之后,那么除了位掩码,EnumSet应该是首选(Effective Java,第2版,第32项)。 - Petr Janeček

1

我认为Java 5之后常量的一个用途是,当常量是方法契约的一部分时(Java 5引入了enum,它们优于位掩码,并且可以在接口上声明),

考虑这样一个接口:

public interface Element {
    Element NONE = new EmptyElement();

    /**
     *  Looks up an element using this element as a root.
     *  If the lookup fails, returns Element.NONE.
     */
    Element findElements(String searchBy);
}

常量是方法合约的一部分,应该放在接口中。在API编程中非常有用,因为您可以为所有人提供默认或特殊的API方法返回值,并进行检查。
最常见的情况是避免模糊的空值返回问题(例如,HashMap#get()方法既会对于缺少的键返回null值,也会对于null值返回null值),但这不仅仅是针对null值 - 任何可能返回的特殊值都应该在接口上定义。

0

接口常量的一个可能用途是确保这些常量在所有实现类中都可见。当然,您可以将所有这些内容放入一个类中,并在这些实现类中使用相同的类,但在规范级别定义常量要容易得多。

这样想吧; 即使这些常量不定义“实现”,它们也存在于任何实现需要它们的情况下。


0

它们可以用于为方法的返回参数/输入参数赋予符号意义。这在预枚举时代是如此。


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