1. 简介

本文将带你深入理解 Java 中常量的使用方式,重点聚焦于常见的模式(Patterns)反模式(Anti-Patterns)

我们会从定义常量的基础约定开始,接着分析一些常见的反模式,最后再介绍一些推荐的最佳实践。

2. 基础知识

常量是定义后值不可更改的变量。

在 Java 中,常量的定义形式如下:

private static final int OUR_CONSTANT = 1;

其中:

  • privatepublic:访问修饰符,根据需要决定是否对外暴露
  • 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)

很多新手喜欢搞一个叫 ConstantsUtils 的类,把所有常量都放进去。

❌ 问题:

  • 类膨胀后难以维护
  • 命名冲突、重复定义的风险增加
  • 逻辑混乱,可读性差
  • 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 为私有,仅类内使用
  • PIOperation 为公共,供外部调用
  • 使用 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 获取。


原始标题:Constants in Java: Patterns and Anti-Patterns