在Java中实现常量的最佳方式是什么?

370

我看过类似这样的例子:

public class MaxSeconds {
   public static final int MAX_SECONDS = 25;
}

我想我可以创建一个常量类来包装常量,将它们声明为静态常量。我几乎不懂Java,想知道这是否是创建常量的最佳方式。


1
只是添加 Java 常量:public/private - ms_27
28个回答

402

那是完全可以接受的,甚至可能是标准。

(public/private) static final TYPE NAME = VALUE;

其中TYPE表示类型,NAME表示大写字母和下划线代替空格的名称,VALUE表示常量值。

我强烈建议不要将常量放在它们自己的类或接口中。

另外注意:声明为final但可变的变量仍然可以更改;但是,该变量永远不会指向不同的对象。

例如:

public static final Point ORIGIN = new Point(0,0);

public static void main(String[] args){

    ORIGIN.x = 3;

}

这是合法的,ORIGIN 将成为点 (3, 0)。


19
你甚至可以使用“import static MaxSeconds.MAX_SECONDS;”来避免重复拼写MaxSeconds.MAX_SECONDS。 - Aaron Maenpaa
23
关于导入静态类的先前评论仅适用于Java 5+。有些人认为这种简写方式不值得可能出现的混淆,当阅读长代码时,使用MaxSeconds.MAX_SECONDS可能更容易跟踪,而不是上下查看导入的内容。 - MetroidFan2002
5
因为你可以像jjnguy展示的那样更改对象,所以最好使用不可变对象或纯基本类型/字符串作为常量。 - marcospereira
14
如果您正在阅读此问题,请在认真考虑答案之前,先阅读下面的两个答案,尽管已被接受,但这个答案具有争议性,甚至可能是错误的。 - orbfish
3
@jjnguy,你说的没错,但如果我只看问题和你回答的前几行,我会误以为“常量类”是“完全可接受的,甚至可能是标准”。这种想法是错误的。 - Zach Lysobey
显示剩余4条评论

234

我强烈建议不要使用单个常量类。虽然在当时可能看起来是一个好主意,但当开发人员拒绝记录常量并且该类涵盖了超过500个与应用程序完全不相关的常量时,通常会导致常量文件变得完全无法阅读。而应该:

  • 如果您可以访问Java 5+,请使用枚举来定义应用程序领域的特定常量。应用程序领域的所有部分都应该引用枚举,而不是常量值,以获取这些常量。您可以声明一个与声明类类似的枚举。枚举可能是Java 5+中最有用(也可能是唯一有用)的功能。
  • 如果您有仅适用于特定类或其子类之一的常量,请将它们声明为受保护或公共,并将它们放置在层次结构中的顶级类上。这样,子类就可以访问这些常量值(如果其他类通过public访问它们,则常量不仅适用于特定类...这意味着使用该常量的外部类可能与包含常量的类过于紧密耦合)
  • 如果您有具有已定义行为的接口,但返回值或参数值应为特定值,则可以在该接口上定义常量,以便其他实现者将其访问。但是,避免创建仅用于保持常量的接口:它可能会变得与仅用于保持常量的类一样糟糕。

12
完全同意... 这可以被记录为大型项目的经典反模式。 - Das
30
离题了,但是……泛型确实不完美,但我认为你可以为它们成为 Java 5 的一个有用特性做出很好的论证 :) - Martin McNulty
3
@ŁukaszL。这个问题本身是真正基于个人意见的(每当“best”出现时,通常就是一个基于个人观点的问题),因此答案对于这个问题来说是有效的答案。我已经给出了一个回答,关于如何(是的,我相信,所以它是一种个人观点,因为“最好”的定义随时间变化,通常是基于个人观点的)在Java中实现常量的最佳方式。 - MetroidFan2002
1
以上选项都不能提供真正的全局变量。如果我有一个在各处都使用但不是枚举的东西,该怎么办?我要创建一个只有一个元素的枚举吗? - markthegrea
1
我同意@markthegrea的观点,仅仅因为有些人没有添加注释或文档常量就不应该使用类。修改常量的人并不都是开发人员。例如,负责纹理文件的人可以简单地更新常量值,重新编译游戏并检查结果,无需查找文件或者想要准备常量(例如限制)文档的人可以参考单个文件而不是搜索所有文件。 - Sachin Gorade
显示剩余2条评论

120

使用接口仅用于保存常量是一种糟糕的实践(由Josh Bloch命名为常量接口模式)。以下是Josh的建议:

如果常量与现有类或接口密切相关,则应将其添加到该类或接口中。例如,所有装箱数值原始类(如Integer和Double)都导出MIN_VALUE和MAX_VALUE常量。如果常量最好被视为枚举类型的成员,则应将它们用enum类型导出。否则,您应该使用不可实例化的实用程序类导出这些常量。

示例:

// Constant utility class
package com.effectivejava.science;
public class PhysicalConstants {
    private PhysicalConstants() { }  // Prevents instantiation

    public static final double AVOGADROS_NUMBER   = 6.02214199e23;
    public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
    public static final double ELECTRON_MASS      = 9.10938188e-31;
}

关于命名规范:

按照约定,这些字段的名称由大写字母组成,单词之间用下划线分隔。关键是这些字段包含原始值或者不可变对象的引用。


23
如果你要称某件事情为不良习惯,也许你应该解释一下为什么这样认为? - GlennS
14
建议使用“抽象”符号表示类,而不是私有构造函数。 - Aritz
5
马特,虽然骑行者的建议没有错,但我更倾向于使用最终类而不是抽象类。骑行者的观点是你要确保你的常量类不可改变。因此,通过将其标记为final,你不允许它被子类化或实例化。这也有助于封装其静态功能,并防止其他开发人员对其进行子类化并使其执行其未设计的任务。 - liltitus27
7
将类标记为abstract而不是私有构造函数并不能完全阻止实例化,因为可以对其进行子类化并实例化子类(虽然这不是一个好主意,但是可能会发生)。您不能对具有私有构造函数的类进行子类化(至少没有通过非常恶劣的黑客手段),因为子类的构造函数需要调用其超类的(现在是私有的)构造函数。因此,将其标记为 final 是不必要的(但可能更加明确)。 - import this
3
仅仅用类来保存常量甚至更糟。如果你从不想要创建该类的对象,为什么一开始要使用普通的“class”呢? - MaxZoom
显示剩余3条评论

36

在《Effective Java》(第二版)中,建议您使用枚举而不是静态整数来表示常量。

这里有一篇关于Java中枚举的好文章: http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html

请注意,在该文章末尾提出了一个问题:

那么什么时候应该使用枚举?

答案是:

任何时候需要一个固定的常量集合时


21

避免使用接口:

public interface MyConstants {
    String CONSTANT_ONE = "foo";
}

public class NeddsConstant implements MyConstants {

}

这很诱人,但违反了封装原则,模糊了类定义的区别。


1
确实,静态导入更可取。 - Aaron Maenpaa
1
这个实践是接口的一种奇怪用法,而接口本意是声明一个类提供的服务。 - Rafa Romero
1
我并不反对避免使用接口来定义常量,但我认为你的例子有误导性。因为你不需要实现该接口就可以访问到这个常量,因为它是隐式的静态、公共和不可变的。所以,它比你在这里描述的更简单。 - djangofan
没错...这违反了封装性,我更喜欢使用final Constant class,它看起来更面向对象,而且比接口或枚举更可取注意:在Android不需要的情况下可以**避免**使用枚举。 - CoDe

18

我使用以下方法:

public final class Constants {
  public final class File {
    public static final int MIN_ROWS = 1;
    public static final int MAX_ROWS = 1000;

    private File() {}
  }

  public final class DB {
    public static final String name = "oups";

    public final class Connection {
      public static final String URL = "jdbc:tra-ta-ta";
      public static final String USER = "testUser";
      public static final String PASSWORD = "testPassword";

      private Connection() {}
    }

    private DB() {}
  }

  private Constants() {}
}

例如,我使用 Constants.DB.Connection.URL 来获取常量。 这样对我来说更具有“面向对象”的特点。


4
有趣但繁琐。为什么不像其他人建议的那样,在与其最相关的类中创建常量呢?例如,在您的DB代码中,是否有一个用于连接的基类?比如,“ConnectionBase”。然后你就可以把常量放在那里。任何使用连接的代码可能已经有了一个导入,可以简单地说“ConnectionBase.URL”,而不是“Constants.DB.Connection.URL”。 - ToolmakerSteve
3
@ToolmakerSteve,那么一些通用常量呢,多个类可以使用它们,例如样式、Web服务URL等等呢? - Daniel Gomez Rico
将所有常量放在一个类中有一个优点 - 维护。您总是知道确切的位置来查找常量。我并不是说这使得这种技术更好,我只是提供了一个优点。而@albus.ua所做的是对其常量进行分类,这是一个相当不错的想法,特别是如果常量类包含许多常量值。这种技术将有助于保持类的可管理性,并有助于更好地描述常量的目的。 - Nelda.techspiress

17

将静态final常量创建在单独的类中可能会引起麻烦。Java编译器实际上会优化这个过程,并将常量的实际值放入任何引用它的类中。

如果随后更改了“Constants”类并且您没有对引用该类的其他类进行硬重编译,则会出现新旧值组合使用的情况。

不要把它们看作常量,而是把它们看作配置参数并创建一个类来管理它们。让这些值不是最终的,甚至可以考虑使用getter方法。在未来,当您确定其中一些参数实际上应由用户或管理员配置时,这将更容易实现。


对于这个优秀的警告点赞。如果您无法保证它是一个永久常量,请考虑使用getter,就像big_peanut_horse提到的那样。顺便说一下,在C#中也适用const:http://msdn.microsoft.com/en-us/library/e6w8fe1b.aspx - Jon Coombs

13

你可能犯的头号错误是创建一个名为Constants等泛称的全局可访问类,它会被垃圾填满,你将失去发现系统中使用这些常量的能力。

相反,常量应该放在“拥有”它们的类中。你有一个叫TIMEOUT的常量吗?它应该放在你的Communications()或Connection()类中。MAX_BAD_LOGINS_PER_HOUR呢?应该放在User()类中。等等。

另一个可能的用途是Java .properties文件,当“常量”可以在运行时定义,但不易于用户更改。你可以将它们打包到你的.jar文件中,并使用Class resourceLoader引用它们。


当然,您永远不会想从多个类中访问常量,或避免在类顶部混乱。 - orbfish

6

这是正确的方法。

通常情况下,常量不会被保存在单独的“常量”类中,因为它们不易被发现。如果常量与当前类有关,则将其保留在该类中可以帮助下一个开发人员。


5
一个枚举怎么样?

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