枚举与接口常量、类常量有什么区别?

本文探讨如何在Java项目中通过接口常量、类常量和枚举来提高商品类型管理的清晰度和安全性。作者介绍了如何使用枚举来组织和限制类型值,以及如何通过单例模式实现线程安全。

一个简单的需求

在我们实际开发java项目过程中,突然有一天"领导老王"给了个任务, 公司系统需要支持商品管理的需求
比如水果有:苹果,香蕉,葡萄等等,电子产品有:电脑,手机,摄像机等等

我们一般新建商品类Goods:

public class Goods {
    /**
     * 商品名称
     */
    private String name;
    /**
     * 商品类型
     */
    private Integer type;

    public Goods(String name, Integer type) {
        this.name = name;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }
}

然后我们就直接可以使用它:

public class GoodsTest {
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods("水果",1);//1代表苹果,2:香蕉,3:葡萄
        System.out.println(goods.getName());
    }
}

但是有个问题,业务代码不清晰,有时候开发人员并不知道1、2、3代表什么意思,而且在业务代码层里面直接写数字或者字符串也是非常危险的时,我们需要一种方案,既能将相关的状态,类型放在一起,又可以限制类的输入值,提升项目的安全性

接口常量

我们可以使用接口常量来解决上面的问题

public interface StatusContentFace {
    public static final String fruit  = "fruit";

    public static final Integer apple  = 1;

    public static final Integer banana  = 2;

    public static final Integer grape  = 3;

    //==========================

    public static final String eleProduct  = "eleProduct";

    public static final Integer computer  = 101;

    public static final Integer phone  = 102;

    public static final Integer camera  = 103;
}

我们再来看下测试类:

public class GoodsTest1 {
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods(StatusContentFace.fruit,StatusContentFace.apple);
        Goods goods_2 = new Goods(StatusContentFace.eleProduct,StatusContentFace.computer);
        System.out.println(goods.getName());
        System.out.println(goods_2.getName());
    }
}

这样能够让相关的常量都在同一个接口文件中,接口常量,写起来比较简洁,但是为了让其他人知道每个常量的含义,最好写上注释。
但它同时有个问题,由于java中接口是支持多继承的

  • 我们可以将内容深入到其实现类代码中,这样对于一个常量类接口来说显然是不合理。
  • 我们还可以在其子接口里继续添加常量,这样在祖先接口中就无法控制所有常量,这样无疑是非常危险的。

一般不建议用的,但接口常量也不是一无是处的,可以通过内部接口来实现分组效果

public class GoodsTest2 {
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods(Fruit.type,Fruit.banana);
        Goods goods_2 = new Goods(EleProduct.type,EleProduct.phone);
        System.out.println(goods.getName());
        System.out.println(goods_2.getName());
    }
    
    //常量分组
    public interface Fruit {
        String type = "fruit";
        Integer apple = 1;
        Integer banana = 2;
        Integer grape = 3;
    }

    public interface EleProduct {
        String type = "eleProduct";
        Integer computer = 101;
        Integer phone = 102;
        Integer camera = 103;
    }
    
}

这样我们可以把相关的常量都归为一类,更加简洁明了

类常量

我们一般常用的是类常量方式:

public final class StatusConstant {
    private StatusConstant() {} //防止该类实例化

    public static final String fruit  = "fruit";

    public static final Integer apple  = 1;

    public static final Integer banana  = 2;

    public static final Integer grape  = 3;

    //==========================

    public static final String eleProduct  = "eleProduct";

    public static final Integer computer  = 101;

    public static final Integer phone  = 102;

    public static final Integer camera  = 103;
}

注意:一般用final关键字修饰 class 防止其被继承,并将其构造函数 private 化,防止被实例化

测试类:

public class GoodsTest3 {
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods(StatusConstant.fruit, StatusConstant.banana);
        Goods goods_2 = new Goods(StatusConstant.eleProduct, StatusConstant.phone);
        System.out.println(goods.getName());
        System.out.println(goods_2.getName());
    }
}

我们可以发现类常量的方式,的确很方便,也没有接口常量多继承的烦恼。但是她所能承接的信息,维度不够,只能一个字段的去承接信息,然而当项目复杂的话,我们希望往往其能承接更多维度的信息,类似于对象一样,拥有更多的属性

{
    "name": ...,
    "type": ...,
     ... 
}

这时候,我们本文的主角,枚举就闪亮登场了!

枚举

什么是枚举?

枚举是一种特殊的类,所有的枚举类都是Enum类的子类,就类似Object类一样,由于java类是单继承的,所以不能在继承其他类或者枚举了。
枚举变量不能使用其他的数据,只能使用枚举中常量赋值。能提高程序的安全性

格式:

public enum 枚举名{ 
  //枚举的取值范围 
} 

枚举常量

我们先定义一个枚举类,来定义常量:

public enum ContentEnums {
    Apple(1,"苹果"),
    Banana(2,"香蕉"),
    Grape(3,"葡萄"),

    Computer(101,"电脑"),
    Phone(102,"手机"),
    Camera(103,"摄像机"),
    

    Fruit(10010,"fruit"),
    EleProduct(10020,"eleProduct");


    private Integer code;
    private String desc;

    ContentEnums(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

测试类:

public class GoodsTest4 {
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods(ContentEnums.Fruit.getDesc(), ContentEnums.Apple.getCode());
        Goods goods_2 = new Goods(ContentEnums.EleProduct.getDesc(), ContentEnums.Phone.getCode());
        System.out.println(goods.getName());
        System.out.println(goods_2.getName());
    }
}

看到这大家可能就有疑问了,枚举常量类相比,有什么优点吗?

  1. 枚举其实是一种特殊的类,可以承接对象的多维信息,但是常量类往往只能承接字段,信息比较单一
  2. 枚举可以搭配switch语句使用,来代替if/else
ContentEnums content = ContentEnums.Apple;

switch (content) {
    case Apple:
        System.out.println("苹果");
        break;
    case Banana:
        System.out.println("香蕉");
        break;
    case Grape:
        System.out.println("葡萄");
        break;
    default:
        System.out.println("未找到匹配类型");
}
  1. enum 有一个非常有趣的特性,它可以为enum实例编写方法
public enum MethodEnums {
    VERSION {
        @Override
        String getInfo() {
            return System.getProperty("java.version");
        }
    },
    DATE_TIME {
        @Override
        String getInfo() {
            return
                    DateFormat.getDateInstance()
                            .format(new Date());
        }
    };
    abstract String getInfo();

    public static void main(String[] args) {
        for(MethodEnums csm : values()) {
            System.out.println(csm.getInfo());
        }

    }
}

结果:

1.8.0_271

2022-9-21

除了抽象方法,普通方法也是可以的,这里就不展示了

  1. 网上还有其他一些优点,感觉没啥特别值得说的

限制输入的类型

我们可以通过枚举来将相关的状态,类型放在一起,文章一开头,但我们怎么才能限制类的输入值呢?其实很简单,别被绕进去,我们只需将输入类型 改为指定的枚举即可
我们改造一下Goods类:

public class Goods {
    /**
     * 商品名称
     */
    private String name;
    /**
     * 商品类型
     */
    private Integer type;

//    public Goods(String name, Integer type) {
//        this.name = name;
//        this.type = type;
//    }

    public Goods() {//防止外部实例化

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public static Goods addGoods(ContentEnums enums){
        Goods goods = new Goods();
        goods.setName(enums.getDesc());
        goods.setType(enums.getCode());
        return goods;
    }
}

测试类:

public class GoodsTest5 {
    public static void main(String[] args) throws InterruptedException {
        Goods goods = Goods.addGoods(ContentEnums.Apple);
        Goods goods_2 = Goods.addGoods(ContentEnums.Computer);
        System.out.println(goods.getName());
        System.out.println(goods_2.getName());
    }
}

这样,我们就可以限制创建对象时的输入值类型了

枚举可以使用==来比较吗?

可以使用==来比较 enum 实例,编译器会自动为你提供equals()和 hashCode() 方法。Enum 类实现了 Comparable接口,所以它具有 compareTo() 方法。同时,它还实现了 Serializable 接口。

枚举实现单例

枚举类型是天生线程安全的,并且只会装载一次,我们可以利用了枚举的这个特性来实现单例

public enum SingleInstance {
    INSTANCE;
    public void funDo() {
          System.out.println("doSomething");
    }
}

使用方式:SingleInstance.INSTANCE.funDo()
这种方法充分 利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。写法也极其简洁。


参考:
《On Java 8》
《Effective java》第3版

 

本文作者:小牛呼噜噜

本文链接:https://www.cnblogs.com/xiaoniuhululu/p/16812466.html

<think>我们被要求对比枚举常量类在消除魔法值时的适用场景。根据引用内容,我们可以总结如下:1.**枚举(Enum)**:适用于表示一组固定的常量集合,且这些常量不仅仅是简单的值,可能还需要承载行为或更复杂的结构(引用[1])。枚举提供型安全,可以避免无效值(引用[5]),并且能够通过枚举的方法(如values(),ordinal(),name())提供便利(引用[2])。枚举还可以消除判断语句,提高代码可维护性(引用[2])。2.**常量类(使用publicstaticfinal字段)**:适用于定义简单的、独立的常量值。当常量之间没有逻辑关联,或者只需要一个简单的值(如最大重试次数、超时时间等)时,使用常量类更简洁。但常量类无法保证型安全,例如在引用[5]中,使用常量类定义的星期常量,在调用方法时可能传入不在常量集范围内的值(如5),而编译器不会报错。###适用场景对比|**特性**|**枚举**|**常量类**||------------------------|-----------------------------------------------|-----------------------------------------------||**适用场景**|一组有限的、预定义的常量(如状态码、型等)|独立的、分散的常量(如配置参数、数学常数)||**型安全**|是(编译期检查)|否(可能传入非法值)[^5]||**可扩展性**|支持添加方法、字段实现接口[^1]|仅支持常量值,无法添加行为||**关联行为**|可在枚举中定义方法实现行为封装[^1]|无法直接关联行为,需借助外部工具||**可读性**|高(枚举值具有语义化名称)|中(依赖命名规范)||**维护性**|高(集中管理,修改枚举定义即可)|中(需查找替换,可能遗漏)||**序列化/反序列化**|内置支持(name()valueOf())[^2]|需手动实现转换逻辑||**多值关联**|支持(如一个枚举可关联codedesc)[^1][^3]|需定义多个常量并手动关联||**集合操作**|支持(values()返回所有枚举)[^2]|需手动维护集合|###具体场景示例####**场景1:订单状态(适合枚举)**```java//枚举实现:状态+行为publicenumOrderStatus{CREATED(1,"已创建"){@OverridepublicbooleancanCancel(){returntrue;}},PAID(2,"已支付"){@OverridepublicbooleancanCancel(){returnfalse;}};privatefinalintcode;privatefinalStringdesc;OrderStatus(intcode,Stringdesc){this.code=code;this.desc=desc;}publicabstractbooleancanCancel();//抽象方法定义状态行为}```**优势**:将状态码、描述行为封装在一起,避免状态判断分散在各处[^1][^3]。---####**场景2:系统配置参数(适合常量类)**```java//常量类实现publicclassSystemConfig{publicstaticfinalintMAX_LOGIN_ATTEMPTS=5;publicstaticfinalStringDEFAULT_TIMEZONE="UTC+8";}```**原因**:这些常量是独立的数值/字符串,无关联行为,也不需要型检查。---####**场景3:错误码映射(适合枚举)**```javapublicenumErrorCode{SUCCESS(0,"成功"),PARAM_ERROR(1001,"参数错误");privatefinalintcode;privatefinalStringmessage;//构造方法及getterpublicstaticErrorCodefromCode(intcode){//查找逻辑}}```**优势**:集中管理错误码描述,并提供code→枚举的转换[^1]。---####**场景4:数学常数(适合常量类)**```javapublicclassMathConstants{publicstaticfinaldoublePI=3.1415926535;publicstaticfinaldoubleGOLDEN_RATIO=1.6180339887;}```**原因**:简单数值常量,无行为关联。---###总结选择原则1.**优先枚举**:当常量满足以下任一条件时:-属于同一逻辑范畴(如状态、型等)-需要关联额外数据(如描述字段)-需要定义基于常量的行为方法-需要确保型安全(避免非法传参)[^5]2.**使用常量类**:当常量:-是全局配置参数(如超时时间)-是数学或物理常数-被多处使用但无逻辑关联>根据阿里巴巴Java开发手册,*状态码、型码等必须使用枚举*[^1][^2]。而似缓存前缀等字符串常量,则适合用常量类定义。§§相关问题§§1.枚举中如何优雅地实现状态机转换?2.常量类在分布式配置场景下的局限性是什么?3.如何通过注解处理器验证魔法值的使用?4.枚举常量类在序列化性能上有何差异?</think>### 枚举常量类在消除魔法值中的适用场景对比 在 Java 开发中,枚举常量类都是消除魔法值的重要手段,但它们的适用场景有显著差异: --- #### **1. 常量类(`public static final`)适用场景** ```java public class SystemConstants { // 简单独立常量 public static final int MAX_RETRY_COUNT = 3; public static final String DEFAULT_TIMEZONE = "UTC+8"; // 技术性配置参数 public static final int CACHE_EXPIRE_SECONDS = 3600; } ``` **适用场景特点**: - **简单独立值**:无逻辑关联的离散值(如数学常数、技术参数) - **纯数值/字符串**:不需要附加行为或描述信息 - **跨模块共享**:被多个不相关使用的全局常量 - **基础配置参数**:如超时时间、重试次数等[^1] > ✅ **典型用例**:连接超时时间、缓存有效期、数学常数(PI)等 --- #### **2. 枚举(`enum`)适用场景** ```java public enum OrderStatus { // 带状态码行为的枚举 CREATED(1, "已创建") { @Override public boolean canCancel() { return true; } }, PAID(2, "已支付") { @Override public boolean canCancel() { return false; } }; private final int code; private final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } public abstract boolean canCancel(); // 抽象方法定义行为 } ``` **适用场景特点**: - **有限状态集合**:固定数量的互斥状态(如订单状态、星期枚举) - **需要关联数据**:每个值需要附加描述、状态码等元数据[^2] - **型安全要求高**:需防止无效值传入(如 `getToday(5)` 会报错)[^5] - **需要封装行为**:不同状态需要不同业务逻辑(如状态流转规则) - **需要映射关系**:状态码 ↔ 描述文本的双向转换[^4] > ✅ **典型用例**:订单状态、错误码体系、业务型等 --- ### **核心差异对比表** | 特性 | 常量类 | 枚举 | |---------------------|--------------------------------|--------------------------------| | **本质** | 静态字段集合 | 特殊(继承`java.lang.Enum`) | | **型安全** | ❌ 可传入任意int/String值 [^5] | ✅ 编译期检查 | | **关联数据能力** | ❌ 只能存储单一值 | ✅ 支持字段+方法封装 [^1][^2] | | **行为封装** | ❌ 无法关联行为 | ✅ 可为每个值实现特定方法 | | **集合操作** | ❌ 需手动维护集合 | ✅ 内置`values()`遍历全部值 | | **序列化/反序列化** | ❌ 需自定义逻辑 | ✅ 内置`name()`/`valueOf()` | | **适用规模** | 任意数量 | 有限集合(通常 < 50个) | --- ### **决策树:如何选择?** ```mermaid graph TD A[需要消除魔法值] --> B{值是否有业务含义?} B -->|是| C{是否属于有限状态集?} C -->|是| D[使用枚举] C -->|否| E[使用常量类] B -->|纯技术参数| E D --> F{需要关联行为?} F -->|是| G[为枚举添加抽象方法] F -->|否| H[基础枚举+字段] ``` > 根据阿里巴巴开发规范:**业务状态码必须使用枚举**,技术参数可使用常量类[^1][^2] --- ### **混合使用案例** ```java // 常量类定义配置 public class CacheConfig { public static final String PREFIX = "ORDER_"; } // 枚举定义状态 public enum CacheType { ORDER_DETAIL(CacheConfig.PREFIX + "DETAIL"), ORDER_LIST(CacheConfig.PREFIX + "LIST"); private final String key; CacheType(String key) { this.key = key; } } ``` > 💡 当枚举需要引用技术常量时,可组合使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值