1. 简介
本文将带你深入理解 Java 中常量的使用方式,重点聚焦于常见的模式(Patterns)和反模式(Anti-Patterns)。
我们会从定义常量的基础约定开始,接着分析一些常见的反模式,最后再介绍一些推荐的最佳实践。
2. 基础知识
常量是定义后值不可更改的变量。
在 Java 中,常量的定义形式如下:
private static final int OUR_CONSTANT = 1;
其中:
private
或public
:访问修饰符,根据需要决定是否对外暴露static
:表示该常量属于类而非实例final
:表示该变量一旦赋值就不能再修改- 类型:可以是 Java 基本类型、类类型,甚至是枚举
- 命名规范:✅ 全大写 + 下划线分隔(Screaming Snake Case)
- 赋值:常量必须在声明时或构造代码块中初始化
3. 反模式
先来看一些不要这么干的写法,避免踩坑。
3.1. 魔数(Magic Numbers)
魔数是指代码中直接出现的、没有明确含义的数值字面量:
if (number == 3.14159265359) {
// ...
}
❌ 问题:
- 难以理解其含义
- 如果多处使用,修改时容易遗漏或出错
✅ 正确做法:定义为常量
private static final double PI = 3.14159265359;
3.2. 全局常量类(Global Constants Class)
很多新手喜欢搞一个叫 Constants
或 Utils
的类,把所有常量都放进去。
❌ 问题:
- 类膨胀后难以维护
- 命名冲突、重复定义的风险增加
- 逻辑混乱,可读性差
- Java 编译器会内联常量值,如果只重新编译常量类而不重新编译引用类,可能导致不一致行为
⚠️ 小项目可能还能接受,但随着项目增长,这种做法会带来灾难。
3.3. 常量接口反模式(Constant Interface Anti-Pattern)
这种做法是定义一个只包含常量的接口,然后让需要这些常量的类去实现它。
示例:
public interface CalculatorConstants {
double PI = 3.14159265359;
double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
enum Operation {ADD, SUBTRACT, MULTIPLY, DIVIDE};
}
实现类:
public class GeometryCalculator implements CalculatorConstants {
public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
// ...
}
}
❌ 问题:
- 接口本意是定义行为契约,不是用来放常量的
- 容易造成字段遮蔽(Field Shadowing)问题
- 污染命名空间:实现类及其子类都会继承接口中的常量
比如:
public static final double UPPER_LIMIT = 100000000000000000000.0;
此时,类中的 UPPER_LIMIT
会覆盖接口中的同名常量,可能引发意料之外的 bug。
4. 推荐模式
4.1. 一般性最佳实践
如果常量和某个类逻辑相关,直接定义在该类内部即可。
如果是枚举类型,优先使用 enum
。
示例:
public class Calculator {
public static final double PI = 3.14159265359;
private static final double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
public enum Operation {
ADD,
SUBTRACT,
DIVIDE,
MULTIPLY
}
public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
if (numberOne > UPPER_LIMIT) {
throw new IllegalArgumentException("'numberOne' is too large");
}
if (numberTwo > UPPER_LIMIT) {
throw new IllegalArgumentException("'numberTwo' is too large");
}
double answer = 0;
switch(operation) {
case ADD:
answer = numberOne + numberTwo;
break;
case SUBTRACT:
answer = numberOne - numberTwo;
break;
case DIVIDE:
answer = numberOne / numberTwo;
break;
case MULTIPLY:
answer = numberOne * numberTwo;
break;
}
return answer;
}
}
✅ 优点:
UPPER_LIMIT
为私有,仅类内使用PI
和Operation
为公共,供外部调用- 使用
enum
限制操作类型,避免非法值传入 - 枚举天然适合
switch
结构
4.2. 包级常量类
如果多个类都需要使用一组相关的常量,可以为该包定义一个常量类。
示例:
public final class MathConstants {
public static final double PI = 3.14159265359;
static final double GOLDEN_RATIO = 1.6180;
static final double GRAVITATIONAL_ACCELERATION = 9.8;
static final double EULERS_NUMBER = 2.7182818284590452353602874713527;
public enum Operation {
ADD,
SUBTRACT,
DIVIDE,
MULTIPLY
}
private MathConstants() {
// 防止实例化
}
}
✅ 特点:
- 类用
final
修饰,防止被继承 - 私有构造函数,防止实例化
public
常量对外暴露,其他为包级私有- 所有常量
static final
,命名规范统一
⚠️ 与全局常量类的区别:
- 范围小:只服务于特定包
- 职责明确:只包含相关常量
- 易维护:不会膨胀成难以管理的“上帝类”
5. 总结
在 Java 中使用常量时,应避免魔数、全局常量类、常量接口等反模式。推荐将常量定义在相关类中,或为特定包创建常量类,使用 enum
来限制值集合。
合理使用常量,不仅提升代码可读性,也能避免维护时的“踩坑”。
本文代码示例可在 GitHub 获取。