Java枚举总结 Java枚举总结作者没有四次元口袋的蓝胖日期2026-06-11标签Java, 枚举, Enum, 设计模式一、枚举是什么枚举enum是一种特殊的类用来表示一组固定的常量集合——值在编译期就确定了运行时不能增减。// 没有枚举之前用常量类publicclassSeason{publicstaticfinalintSPRING1;publicstaticfinalintSUMMER2;publicstaticfinalintAUTUMN3;publicstaticfinalintWINTER4;}// 问题类型不安全可以传任意int值// 用枚举类型安全只能传定义好的值publicenumSeason{SPRING,SUMMER,AUTUMN,WINTER}枚举 vs 常量类的核心优势类型安全。编译器帮你检查传错值直接报错。二、枚举基础语法2.1 定义枚举publicenumSeason{// 枚举值必须放在最前面逗号分隔分号结尾SPRING(春天,万物复苏),SUMMER(夏天,烈日炎炎),AUTUMN(秋天,秋高气爽),WINTER(冬天,银装素裹);// 成员变量privatefinalStringname;privatefinalStringdesc;// 构造方法默认private也只能是privateSeason(Stringname,Stringdesc){this.namename;this.descdesc;}// getter方法publicStringgetName(){returnname;}publicStringgetDesc(){returndesc;}}关键规则枚举值必须写在最前面用逗号分隔最后分号构造方法只能是private不写也是private枚举不能new只能用定义好的值枚举默认继承java.lang.Enum不能继承其他类2.2 使用枚举SeasonsSeason.SPRING;System.out.println(s.getName());// 春天System.out.println(s.getDesc());// 万物复苏// switch中使用枚举是最佳switch搭档switch(s){caseSPRING:System.out.println(春天来了);break;caseSUMMER:System.out.println(夏天来了);break;// 不需要default——枚举值固定编译器能检查是否覆盖}面试题“switch能传枚举吗”→ 能而且比传String/int更好。枚举值固定编译器能检查是否遗漏了case传String/int则无法检查。2.3 Enum常用方法SeasonsSeason.SPRING;s.name()// SPRING —— 枚举值的名称字符串s.ordinal()// 0 —— 枚举值的序号从0开始s.toString()// SPRING —— 默认同name()可重写Season.valueOf(SPRING)// Season.SPRING —— 按名称获取枚举值Season.values()// {SPRING, SUMMER, AUTUMN, WINTER} —— 所有枚举值s.compareTo(Season.SUMMER)// -1 —— 比较序号差坑点ordinal()// ordinal()返回声明顺序从0开始SPRING.ordinal()// 0SUMMER.ordinal()// 1AUTUMN.ordinal()// 2WINTER.ordinal()// 3// ❌ 不要用ordinal()做业务逻辑// 如果中间插入一个新枚举值ordinal全部变化业务逻辑会出错// ✅ 应该定义自己的属性字段面试题“values()和valueOf()是哪里来的Enum类里没有这两个方法。”→ 这是编译器添加的静态方法。编译enum时Java编译器自动生成values()和valueOf(String)两个静态方法。所以它们在Enum的API文档里找不到。三、枚举的本质3.1 枚举其实是个类publicenumSeason{SPRING,SUMMER,AUTUMN,WINTER;}编译后等价于publicfinalclassSeasonextendsEnumSeason{publicstaticfinalSeasonSPRINGnewSeason(SPRING,0);publicstaticfinalSeasonSUMMERnewSeason(SUMMER,1);publicstaticfinalSeasonAUTUMNnewSeason(AUTUMN,2);publicstaticfinalSeasonWINTERnewSeason(WINTER,3);privatestaticfinalSeason[]$VALUES;static{$VALUESnewSeason[]{SPRING,SUMMER,AUTUMN,WINTER};}publicstaticSeason[]values(){return$VALUES.clone();}publicstaticSeasonvalueOf(Stringname){returnEnum.valueOf(Season.class,name);}// 私有构造privateSeason(Stringname,intordinal){super(name,ordinal);}}核心要点枚举类默认final不能被继承每个枚举值是类的一个静态final实例枚举继承自java.lang.Enum所以不能再继承其他类但可以实现接口构造方法私有外部不能new3.2 枚举的单例性每个枚举值在JVM中只有一个实例——天然单例线程安全防反射攻击。Seasons1Season.SPRING;Seasons2Season.SPRING;System.out.println(s1s2);// true同一个对象四、枚举高级用法4.1 枚举实现接口枚举不能继承类但可以实现接口——这是扩展枚举行为的主要方式。publicinterfacePrintable{Stringformat();}publicenumColorimplementsPrintable{RED(红色){OverridepublicStringformat(){return【getName()】;}},GREEN(绿色){OverridepublicStringformat(){returngetName();}},BLUE(蓝色){OverridepublicStringformat(){return**getName()**;}};privatefinalStringname;Color(Stringname){this.namename;}publicStringgetName(){returnname;}}Color.RED.format()// 【红色】Color.GREEN.format()// 绿色两种实现方式枚举类统一实现接口方法——所有枚举值共用一个实现每个枚举值各自覆盖实现——如上例每个值有不同行为这种方式也叫枚举常量特定方法4.2 枚举中定义抽象方法publicenumOperation{ADD(){Overridepublicdoubleapply(doublea,doubleb){returnab;}},SUBTRACT(-){Overridepublicdoubleapply(doublea,doubleb){returna-b;}},MULTIPLY(×){Overridepublicdoubleapply(doublea,doubleb){returna*b;}},DIVIDE(÷){Overridepublicdoubleapply(doublea,doubleb){returna/b;}};privatefinalStringsymbol;Operation(Stringsymbol){this.symbolsymbol;}publicStringgetSymbol(){returnsymbol;}// 抽象方法——强制每个枚举值必须实现publicabstractdoubleapply(doublea,doubleb);}Operation.ADD.apply(3,4)// 7.0Operation.MULTIPLY.apply(3,4)// 12.0这种模式叫策略枚举——每个枚举值就是一个策略实现。比if-else/switch更优雅。4.3 枚举单例模式《Effective Java》推荐的最佳单例实现方式publicenumSingleton{INSTANCE;privatefinalDataSourcedataSource;Singleton(){dataSourcecreateDataSource();}publicDataSourcegetDataSource(){returndataSource;}}// 使用Singleton.INSTANCE.getDataSource();为什么枚举单例是最好的实现方式线程安全防反射防序列化破坏代码简洁饿汉式✅❌❌✅DCL懒汉式✅❌❌❌ 复杂静态内部类✅❌❌✅枚举✅✅✅✅枚举单例三防线程安全类加载时初始化JVM保证防反射反射尝试通过Constructor创建枚举实例时JVM会抛IllegalArgumentException防序列化破坏枚举的序列化/反序列化由JVM特殊处理不会创建新对象面试高频“用枚举实现单例的原理为什么防反射”→ 枚举值在类加载时由JVM创建反射API在newInstance()中检查了枚举类型如果是枚举直接抛异常。源码层面是Constructor.newInstance()中有if ((clazz.getModifiers() Modifier.ENUM) ! 0) throw new IllegalArgumentException(...)。4.4 枚举配合Map/集合// EnumMap——枚举专用的Map性能极高MapSeason,StringmapnewEnumMap(Season.class);map.put(Season.SPRING,万物复苏);map.put(Season.SUMMER,烈日炎炎);// EnumSet——枚举专用的SetEnumSetSeasonallEnumSet.allOf(Season.class);// {SPRING, SUMMER, AUTUMN, WINTER}EnumSetSeasonrangeEnumSet.range(Season.SPRING,Season.AUTUMN);// {SPRING, SUMMER, AUTUMN}EnumSetSeasoncomplementEnumSet.complementOf(range);// {WINTER}EnumMap为什么快→ 内部用数组存储key的ordinal()就是数组下标O(1)直接定位不需要hash计算和冲突处理。EnumSet为什么快→ 内部用bit向量存储一个long的64bit就够存64个枚举值位运算极快内存极小。五、枚举的坑5.1 坑1ordinal()不要用于业务// ❌ 用ordinal做数据库存储——中间加一个枚举值全乱publicenumStatus{ACTIVE,// ordinal0INACTIVE,// ordinal1DELETED// ordinal2}// 如果在ACTIVE后面加了PENDINGINACTIVE和DELETED的ordinal都变了// ✅ 定义code字段做业务标识publicenumStatus{ACTIVE(1),INACTIVE(2),DELETED(3);privatefinalintcode;Status(intcode){this.codecode;}publicintgetCode(){returncode;}}5.2 坑2枚举不能继承// ❌ 编译错误枚举不能继承类publicenumColorextendsBaseColor{...}// ✅ 替代方案实现接口publicenumColorimplementsColorBehavior{...}5.3 坑3枚举值之间用逗号不是分号// ❌ 编译错误publicenumColor{RED;// 这是分号不是逗号GREEN;BLUE;}// ✅ 正确publicenumColor{RED,GREEN,BLUE;// 逗号分隔最后分号结尾}5.4 坑4枚举的valueOf区分大小写Season.valueOf(SPRING)// ✅ 正确Season.valueOf(spring)// ❌ IllegalArgumentException// 必须和枚举值名称完全一致5.5 坑5switch中不要每个case都new对象// ❌ 每次switch都创建新对象浪费switch(type){caseA:returnnewStrategyA();caseB:returnnewStrategyB();}// ✅ 用枚举每个策略就是枚举值天然单例switch(type){caseA:returnStrategy.A;// 不用newcaseB:returnStrategy.B;}六、枚举的实际应用场景6.1 状态机publicenumOrderState{CREATED{OverridepublicOrderStatenext(){returnPAID;}},PAID{OverridepublicOrderStatenext(){returnSHIPPED;}},SHIPPED{OverridepublicOrderStatenext(){returnCOMPLETED;}},COMPLETED{OverridepublicOrderStatenext(){returnthis;}// 终态};publicabstractOrderStatenext();}OrderStatestateOrderState.CREATED;statestate.next();// PAIDstatestate.next();// SHIPPED6.2 错误码publicenumErrorCode{SUCCESS(200,操作成功),BAD_REQUEST(400,请求参数错误),UNAUTHORIZED(401,未授权),FORBIDDEN(403,禁止访问),NOT_FOUND(404,资源不存在),INTERNAL_ERROR(500,服务器内部错误);privatefinalintcode;privatefinalStringmessage;ErrorCode(intcode,Stringmessage){this.codecode;this.messagemessage;}publicintgetCode(){returncode;}publicStringgetMessage(){returnmessage;}}6.3 策略模式用枚举替代if-else/switch策略选择publicenumDiscountStrategy{NORMAL{Overridepublicdoublecalculate(doubleprice){returnprice;}},VIP{Overridepublicdoublecalculate(doubleprice){returnprice*0.8;}},SVIP{Overridepublicdoublecalculate(doubleprice){returnprice*0.6;}};publicabstractdoublecalculate(doubleprice);}// 使用——不需要if-elseDiscountStrategystrategyDiscountStrategy.VIP;doublefinalPricestrategy.calculate(100.0);// 80.0七、面试高频题Q1枚举和常量类有什么区别对比项常量类枚举类型安全可以传任意同类型值只能传定义好的值可读性只有数值含义不明名称即含义可扩展可以加字段和方法同样可以switch支持支持int/String支持且编译器检查完整性单例保证无天然单例可遍历需要自己维护集合values()直接遍历Q2枚举能继承吗能实现接口吗不能继承任何类已经继承了EnumJava单继承能实现接口这是扩展枚举行为的主要方式Q3枚举为什么是最好的单例实现三防线程安全类加载机制保证、防反射JVM源码层面拦截、防序列化破坏JVM特殊处理枚举的反序列化。Q4EnumMap和HashMap的区别EnumMap内部用数组ordinal下标O(1)直接定位不需要hash和冲突处理。HashMap需要hash计算、处理冲突。枚举做key时EnumMap快得多。Q5枚举的构造方法为什么只能是private枚举的设计目标是值固定、实例数量固定。如果构造方法公开外部就能new出新实例破坏枚举的固定性。Java编译器强制构造方法为private。思维导图速览Java枚举Enum ├── 基础 │ ├── 本质final class extends Enum │ ├── 构造方法只能private │ ├── 枚举值静态final实例天然单例 │ └── 常用方法name()/ordinal()/values()/valueOf() ├── 高级用法 │ ├── 枚举字段构造方法带属性的枚举 │ ├── 枚举实现接口 │ ├── 枚举定义抽象方法策略枚举 │ └── 枚举单例Effective Java推荐 ├── 专用集合 │ ├── EnumMap → 数组ordinalO(1) │ └── EnumSet → bit向量极速 ├── 应用场景 │ ├── 状态机 │ ├── 错误码 │ ├── 策略模式 │ └── 配置项/选项 ├── 常见坑 │ ├── ordinal()不要用于业务 │ ├── valueOf区分大小写 │ ├── 枚举值用逗号不是分号 │ └── 枚举不能继承 └── 面试必背 ├── 枚举vs常量类 → 类型安全 ├── 枚举单例三防 → 线程安全/防反射/防序列化 ├── 枚举不能继承但能实现接口 └── EnumMap原理 → 数组ordinal下标写在最后枚举在Java中是一个小而精的特性——语法简单但内涵丰富。初学时觉得就是一组常量深入后发现它其实是面向对象的常量定义基础层面替代常量类类型安全可读性强进阶层面带字段和方法实现接口定义抽象方法——本质就是特殊的类高阶层面枚举单例、策略枚举、状态机——设计模式的利器面试中枚举的高频考点就三个枚举单例为什么最好、枚举本质编译后是什么样的、枚举vs常量类的区别。把这三点吃透枚举部分基本没问题。实际开发中凡是值固定、有限可列的场景都应该用枚举状态码、类型分类、配置选项、策略选择。用枚举替代常量类是代码质量提升的第一步。