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

370

我看过类似这样的例子:

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

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


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

5

我认为使用接口并不是明智之举。避免使用这种模式在Bloch的《Effective Java》中甚至有自己的一项(#18)。

Bloch反对使用常量接口模式的一个论点是,使用常量是一种实现细节,但通过实现一个接口来使用它们,在你导出的API中暴露了这个实现细节。

public|private static final TYPE NAME = VALUE;模式是声明常量的好方式。个人认为最好避免创建一个单独的类来存放所有的常量,但除了个人偏好和风格外,我从未见过不这样做的理由。

如果您的常量可以很好地建模为枚举,请考虑1.5或更高版本中提供的枚举结构

如果您使用的版本早于1.5,仍然可以使用普通的Java类来实现类型安全的枚举。(请参见此网站获取更多信息)。


一些常量可用于调用API。例如,请参见接口org.springframework.transaction.TransactionDefinition。它有一个常量列表,如int PROPAGATION_REQUIRED = 0;。 - borjab
我知道这是老问题了,但是链接到Bloch的Effective Java已经失效了。你能提供另一个链接或者其他支持“使用接口不是正确的方法”的参考资料吗? - Nelda.techspiress

5
我更喜欢使用getter而不是常量。这些getter可能会返回常量值,例如public int getMaxConnections() {return 10;},但任何需要常量的东西都将通过getter进行访问。
其中一个好处是,如果您的程序超出了常量 - 您发现它需要进行配置 - 您只需更改getter如何返回常量即可。
另一个好处是,为了修改常量,您不必重新编译使用它的所有内容。当您引用静态final字段时,该常量的值编译到引用它的任何字节码中。

5
在21世纪重新编译引用类几乎不是负担。除了访问和修改成员变量之外,您永远不应该使用访问器/修改器(getter/setter)模型来处理其他内容。 常量在概念上意味着是立即数,而getter/setter则是(都)用于管理状态。此外,这样做只会引起混淆:人们不会期望getter仅返回常量。 - user4229245

4

根据上面的评论,我认为这是一种好的方法来改变旧式的全局常量类(具有公共静态最终变量),以其类似于枚举的等效方式:

public class Constants {

    private Constants() {
        throw new AssertionError();
    }

    public interface ConstantType {}

    public enum StringConstant implements ConstantType {
        DB_HOST("localhost");
        // other String constants come here

        private String value;
        private StringConstant(String value) {
            this.value = value;
        }
        public String value() {
            return value;
        }
    }

    public enum IntConstant implements ConstantType {
        DB_PORT(3128), 
        MAX_PAGE_SIZE(100);
        // other int constants come here

        private int value;
        private IntConstant(int value) {
            this.value = value;
        }
        public int value() {
            return value;
        }
    }

    public enum SimpleConstant implements ConstantType {
        STATE_INIT,
        STATE_START,
        STATE_END;
    }

}

那么我可以将它们引用为:

如下:

Constants.StringConstant.DB_HOST

4
为什么?这怎么算是一种改进呢?现在每个引用都很麻烦(Constants.StringConstant.whatever)。我认为你在这里走上了一条崎岖不平的道路。 - ToolmakerSteve

3
一个好的面向对象设计不应该需要很多公开可用的常量。大多数常量应该封装在需要它们来完成工作的类中。

1
或者通过构造函数注入到那个类中。 - WW.

2
什么是在Java中实现常量的最佳方法?
一个应该避免的方法:使用接口来定义常量。
专门创建一个接口来声明常量确实是最糟糕的事情:这违背了接口设计的原因:定义方法合同。
即使已经存在一个接口来解决特定需求,将常量声明在其中也没有意义,因为常量不应该成为API和提供给客户类的合同的一部分。
为简化起见,我们有四种有效方法。
使用静态final String / Integer字段:
1)在类内部但不仅声明常量的类。 1变体)创建一个专门用于声明常量的类。
使用Java 5枚举:
2)在相关目的类中声明枚举(因此作为嵌套类)。 2变体)将枚举创建为独立的类(因此在其自己的类文件中定义)。
TLDR:哪种方法最好,常量在哪里?
在大多数情况下,枚举方式可能比静态final String / Integer方式更好,并且我个人认为只有在有充分的理由不使用枚举时才应使用静态final String / Integer方式。 关于在哪里声明常量值,想法是搜索是否有单个现有类拥有与常量值具有特定和强大的功能内聚性。如果找到这样的类,则应将其用作常量持有者。否则,该常量不应与任何特定类相关联。
静态final String / Integer与枚举
枚举使用确实是一种需要强烈考虑的方式。 枚举比String或Integer常量字段具有更大的优势。 它们设置了更强的编译约束。 如果定义一个以枚举为参数的方法,则只能传递在枚举类中定义的枚举值(或null)。 对于String和Integer,您可以将它们替换为任何兼容类型的值,即使该值不是在静态final String / Integer字段中定义的常量,编译也会很好。
例如,下面两个常量在类中定义为静态final String字段:
public class MyClass{

   public static final String ONE_CONSTANT = "value";
   public static final String ANOTHER_CONSTANT = "other value";
   . . .
}

这里有一个期望参数为以下常量之一的方法:

public void process(String constantExpected){
    ...    
}

你可以用以下方式调用它:
process(MyClass.ONE_CONSTANT);

或者
process(MyClass.ANOTHER_CONSTANT);

但没有编译限制阻止您以这种方式调用它:
process("a not defined constant value");

您只有在运行时并且仅在一次检查传输值的情况下才会遇到错误。

使用枚举,无需进行检查,因为客户端只能在枚举参数中传递枚举值。

例如,在此处定义了两个值的枚举类(因此是开箱即用的常量):

public enum MyEnum {

    ONE_CONSTANT("value"), ANOTHER_CONSTANT(" another value");

    private String value;

    MyEnum(String value) {
       this.value = value;
    }
         ...
}

这里有一个期望将其中一种枚举值作为参数的方法:

public void process(MyEnum myEnum){
    ...    
}

你可以这样调用它:
process(MyEnum.ONE_CONSTANT);

或者
process(MyEnum.ANOTHER_CONSTANT);

但编译过程不允许您以这种方式调用它:

process("a not defined constant value");

常量应该在哪里声明?

如果您的应用程序包含一个具有特定和强大的功能内聚性的单个现有类,那么1)和2)更直观。
通常,如果这些常量在操作它们的主要类中声明或者有一个非常自然的名字,我们可以猜到会在其中找到它们,这样会更容易使用这些常量。

例如,在JDK库中,指数和pi常量值在一个声明不仅包括常量声明的类中声明(java.lang.Math)。

   public final class Math {
          ...
       public static final double E = 2.7182818284590452354;
       public static final double PI = 3.14159265358979323846;
         ...
   }

客户经常使用数学函数,依赖于 Math 类。因此,他们可以很容易地找到常量,并且也可以自然地记住 EPI 的定义位置。
如果您的应用程序没有现有类与常量值具有非常特定和强大的功能内聚性,则第一种和第二种方法更加直观。
通常,如果这些常量在一个类中声明并进行操作,而我们还有其他 3 或 4 个类同样操作它们,而这些类中没有一个比其他类更自然地托管常量值,则不会简化常量的使用。
在这种情况下,定义一个自定义类仅包含常量值是有意义的。
例如,在 JDK 库中,java.util.concurrent.TimeUnit 枚举类型未在特定类中声明,因为实际上不存在一个而且只有一个 JDK 特定类最直观地承载它。
public enum TimeUnit {
    NANOSECONDS {
      .....
    },
    MICROSECONDS {
      .....
    },
    MILLISECONDS {
      .....
    },
    SECONDS {
      .....
    },
      .....
}      

许多在java.util.concurrent中声明的类使用它们: BlockingQueue, ArrayBlockingQueue<E>, CompletableFuture, ExecutorService等等,似乎没有任何一个更适合容纳枚举。


2

对于这个问题,有一定的不同意见。首先,在Java中,常量通常被声明为public、static和final。以下是原因:

public, so that they are accessible from everywhere
static, so that they can be accessed without any instance. Since they are constants it
  makes little sense to duplicate them for every object.
final, since they should not be allowed to change

我不会将接口用于常量访问器/对象,因为通常期望实现接口。这看起来很奇怪:
String myConstant = IMyInterface.CONSTANTX;

相反,我会在一些小的权衡之间进行选择,因此取决于您的需求,以下是几种不同的方式可供选择:

1.  Use a regular enum with a default/private constructor. Most people would define 
     constants this way, IMHO.
  - drawback: cannot effectively Javadoc each constant member
  - advantage: var members are implicitly public, static, and final
  - advantage: type-safe
  - provides "a limited constructor" in a special way that only takes args which match
     predefined 'public static final' keys, thus limiting what you can pass to the
     constructor

2.  Use a altered enum WITHOUT a constructor, having all variables defined with 
     prefixed 'public static final' .
  - looks funny just having a floating semi-colon in the code
  - advantage: you can JavaDoc each variable with an explanation
  - drawback: you still have to put explicit 'public static final' before each variable
  - drawback: not type-safe
  - no 'limited constructor'

3.  Use a Class with a private constructor:
  - advantage: you can JavaDoc each variable with an explanation
  - drawback: you have to put explicit 'public static final' before each variable
  - you have the option of having a constructor to create an instance
     of the class if you want to provide additional functions related
     to your constants 
     (or just keep the constructor private)
  - drawback: not type-safe

4. Using interface:
  - advantage: you can JavaDoc each variable with an explanation
  - advantage: var members are implicitly 'public static final'
  - you are able to define default interface methods if you want to provide additional
     functions related to your constants (only if you implement the interface)
  - drawback: not type-safe

1

任何类型的常量都可以通过在类中创建一个不可变属性(即带有final修饰符的成员变量)来声明。通常还提供staticpublic修饰符。

public class OfficePrinter {
    public static final String STATE = "Ready";  
}

有很多应用程序中,常量的值表示从 n 元组(例如枚举)中进行选择。在我们的示例中,我们可以选择定义一个枚举类型,以限制可能分配的值(即改进类型安全性):

public class OfficePrinter {
    public enum PrinterState { Ready, PCLoadLetter, OutOfToner, Offline };
    public static final PrinterState STATE = PrinterState.Ready;
}

1

什么是区别

1.

public interface MyGlobalConstants {
    public static final int TIMEOUT_IN_SECS = 25;
}

2。

public class MyGlobalConstants {
    private MyGlobalConstants () {} // Prevents instantiation
    public static final int TIMEOUT_IN_SECS = 25;
}

并且在需要使用这个常量的地方,使用MyGlobalConstants.TIMEOUT_IN_SECS。我认为两者是相同的。


1
这似乎基本上是对bincob答案的评论。我认为它们的行为非常相似,但bincob的观点是它们并没有以任何方式定义接口。建议是将常量添加到实际类中,而不是创建一个MyGlobalConstants骨架类。(尽管这有时是有意义的;通过具有静态常量和私有构造函数来防止实例化使用“静态类”;请参见java.lang.math。)考虑使用枚举。 - Jon Coombs
同时,在类声明中加入“final”关键字可以防止子类化。(在C#中,您可以使用“static”关键字,它意味着“final abstract”,因此不需要显式的私有构造函数。) - Jon Coombs
是的,@JonCoombs 但是 "final" 并不能防止它的直接实例化。而且 Java 不允许 final 和 abstract 同时出现在类中,因此会出现无限出现的私有构造函数来防止实例化和子类化。我不知道为什么 "final abstract" 被禁止,除了乍一看似乎在阅读方式上存在矛盾之外:"你不能子类化,但这个类是用来被子类化的"。 - user4229245

1

一个单一的、通用的常量类是一个不好的想法。常量应该与它们最相关的类一起分组。

与其使用任何类型的变量(特别是枚举),我建议您使用方法。创建一个与变量同名的方法,并使其返回您分配给变量的值。现在删除变量,并将所有引用它的地方替换为对您刚刚创建的方法的调用。如果您觉得这个常量足够通用,不需要创建类的实例来使用它,那么将常量方法设置为类方法。


1

顺便提一下,超时时间应该作为配置设置(从属性文件中读取或通过Spring注入)而不是常量。


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