【Java基础】Java枚举类详解

一 概念

Java枚举(Enum)是一种特殊的类,用于表示一组固定且有限的常量(如季节、状态、星期等),替代常量类/接口,如星期、状态码、方向等,避免“魔法值”,并提供类型安全性和代码可读性。

二 基础特性
1 构造器私有化

枚举的构造器默认private,禁止外部实例化

2 类型安全

枚举常量本质是public static final枚举类实例,确保该常量不可修改(安全)且能被外部直接访问

3 单例性

每个枚举项在JVM中仅有一个实例(每个枚举项是单例的,而非每个枚举类只有一个实例)

4 继承性

枚举类继承java.lang.Enum类且本身不能被继承

5 数据完整性

枚举类内部成员变量由private修饰,且对外部只提供get只读方法

三 自定义枚举类

Java 5.0之前,由于缺少enum关键字,开发者需通过自定义类实现枚举功能

//类由 final 修饰不可以被继承
public final class Season{
    //实例由 public static final 修饰 全局暴露常量
    public static final Season SPRING = new Season();
    public static final Season SUMMER = new Season("夏季","炎炎夏日");
    public static final Season AUTUMN = new Season();
    public static final Season WINTER = new Season("冬季","寒风凛冽");

    //枚举类成员属性用private修饰,防止外部直接修改,对外只提供get方法读取(虽然用其他修饰符也不会报错)
    private String name;
    private String desc;

    private Season(){
        //​构造器由 private 修饰,禁止外部创建实例
    }
    private Season(String name, String desc) {
        //​构造器由 private 修饰,禁止外部创建实例
        this.name = name;
        this.desc = desc;
    }
    //保留get方法,确保属性只读。
    public String getName() {
        return name;
    }
    //保留get方法,确保属性只读。
    public String getDesc() {
        return desc;
    }
}

以上代码可以看出该自定义枚举类有4个实例,分别是 SPRING,SUMMER,AUTUMN,WINTER,该4个实例在JVM中只有一个,并且不能被外部改写

四 枚举类

虽然自定义枚举类可以实现枚举类的特点,但是用枚举类实现更简洁快速,且代码可读性高,扩展性强,下面用枚举类实现该特点

注意

1 常量名必须在第一行,且以逗号分隔分号结束,常量名全大写,符合常量命名惯例
2 枚举类隐式实现 java.lang.Enum类,不能继承其他类,但可以实现接口
3 枚举类本身被final修饰,不能被其他类继承
4 构造器隐式添加private修饰符,强行添加其他访问修饰符会报错
5 常量名隐式被public static final修饰,不能添加其他修饰符
6 为了符合枚举类特点,枚举类成员变量用private修饰,对外提供读取访问方法

语法

修饰符 enum 枚举类名{
    常量名1,常量名2,常量名3,常量名3,常量名4(实参);
    private 数据类型 变量名;
    枚举类名() {} //无参构造器1 小括号可以省略
    枚举类名(形参) {} //有参构造器2
    public 数据类型 get变量名() {
        return 变量名;
    }
}

具体实现

public enum Season {
    //常量名全大写(如SPRING),符合常量命名惯例
    SPRING,  //小括号可以省略
    SUMMER("夏季", "炎炎夏日"),
    AUTUMN(),
    WINTER("冬季", "寒风凛冽");

    //枚举类成员属性用private修饰,防止外部直接修改,对外只提供get方法读取(虽然用其他修饰符也不会报错)
    private String name;
    private String desc;

    Season() {

    }
    Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }
}

四 与enum关键字的对比

特性 自定义枚举类 enum关键字​(JDK5.0+)
继承 可自由继承其他类 隐式继承java.lang.Enum,无法再继承其他类
方法支持 需手动实现(如遍历、按名获取) 自动生成values()、valueOf()等方法
语法简洁性 冗长(需显式声明对象和修饰符) 简洁(SPRING("春")一行定义)等

五 枚举类的常用方法

​ 方法 功能 示例
values() 返回所有枚举项的数组 Season[] seasons = Season.values();
ordinal() 返回枚举项的索引(从0开始) int index = Season.SPRING.ordinal();
valueOf(String) 根据名称返回枚举项(需完全匹配) Season s = Season.valueOf("SUMMER");
name() 返回枚举项名称(同 toString() 默认行为) String name = Season.WINTER.name();
compareTo(E o) 返回两个常量的索引差 int i = Season.SPRING.compareTo(Season.AUTUMN);

测试代码如下

//测试枚举类
public enum Season {
    SPRING("春季", "春暖花开"),
    SUMMER("夏季", "炎炎夏日"),
    AUTUMN("秋季", "秋高气爽"),
    WINTER("冬季", "寒风凛冽");

    //枚举类成员属性用private修饰,防止外部直接修改,对外只提供get方法读取(虽然用其他修饰符也不会报错)
    private String name;
    private String desc;

    Season() {

    }

    Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return name;
    }
}

测试values()方法、ordinal()方法

//测试方法类
public class Test {
    public static void main(String[] args) {
        // values()方法是静态方法,可以直接用枚举类名.values()调用
        Season[] seasons = Season.values();
        for (int i = 0; i < seasons.length; i++) {
            // Season重写了toString方法,会输出 Season对象的name
            // ordinal() 返回枚举项的索引(从0开始)
            System.out.println(seasons[i].toString() + " " + seasons[i].ordinal());
        }
    }
}

结果如下:

测试valueOf(String)方法

public class Test {
    public static void main(String[] args) {
        // valueOf(String)方法根据名称(枚举常量名)返回枚举项(需完全匹配),不完全匹配会抛出 java.lang.IllegalArgumentException异常
        Season spring = Season.valueOf("SPRING");
        System.out.println(spring);
        Season summer = Season.valueOf("summer");
        System.out.println(summer);
    }
}

结果如下:

测试name()方法

public class Test {
    public static void main(String[] args) {
        Season[] seasons = Season.values();
        for (int i = 0; i < seasons.length; i++) {
            // name()方法返回枚举项名称
            System.out.println(seasons[i].name());
        }
    }
}

结果如下:

测试compareTo() 方法

public class Test {
    public static void main(String[] args) {
        System.out.println(Season.SPRING.ordinal());
        System.out.println(Season.AUTUMN.ordinal());
        //compareTo()比较方法会返回两个常量的索引差
        System.out.println(Season.SPRING.compareTo(Season.AUTUMN));
    }
}

结果如下:

注意:Java枚举中的values()方法是一个特殊的存在:​它并非由java.lang.Enum类提供,而是由Java编译器(javac)在编译枚举类时自动生成的

六 枚举实现接口和抽象方法

在Java中,枚举类实现接口的核心价值在于为每个枚举常量赋予独立的行为能力,从而实现常量级的多态性。以下是具体实现方式、技术细节及典型应用场景的详解

​1 定义接口​
声明接口并定义抽象方法,明确枚举需要实现的行为规范:

public interface Play {
    void play();  // 抽象方法声明
}

2 实现接口

第一种方式实现接口,枚举类级统一实现

public enum Season implements Play{
    SPRING("春季", "春暖花开"),
    SUMMER("夏季", "炎炎夏日"),
    AUTUMN("秋季", "秋高气爽"),
    WINTER("冬季", "寒风凛冽");

    //枚举类成员属性用private修饰,防止外部直接修改,对外只提供get方法读取(虽然用其他修饰符也不会报错)
    private String name;
    private String desc;

    Season() {

    }

    Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    @Override
    public void play() {
        System.out.println("枚举类级统一实现");
    }
    
    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return name;
    }
}

​第二种方式实现接口,常量级实现,并为每个常量重写接口方法

public enum Season implements Play{
    SPRING("春季", "春暖花开") {
        @Override
        public void play() {
            System.out.println("春天要来了");
        }
    },
    SUMMER("夏季", "炎炎夏日") {
        @Override
        public void play() {
            System.out.println("夏天要来了");
        }
    },
    AUTUMN() {
        @Override
        public void play() {
            System.out.println("秋天要来了");
        }
    },
    WINTER {
        @Override
        public void play() {
            System.out.println("冬天要来了");
        }
    };

    //枚举类成员属性用private修饰,防止外部直接修改,对外只提供get方法读取(虽然用其他修饰符也不会报错)
    private String name;
    private String desc;

    Season() {

    }

    Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return name;
    }
}

关键点​:
每个常量通过匿名内部类语法{ ... }重写方法,实现行为差异化
若所有常量行为一致,可在枚举类级别统一实现接口方法(不推荐,会丧失多态性)

3 行为多态化的典型场景

不同状态/策略执行不同逻辑,例如订单状态流转:

public enum OrderStatus implements StateHandler {
    CREATED {
        @Override
        public void handle() { /* 创建订单逻辑 */ }
    },
    PAID {
        @Override
        public void handle() { /* 支付处理逻辑 */ }
    };
}

4 两种实现接口差异

实现方式 代码示例 适用场景
枚举类级统一实现 num Season implements Displayable { ... @Override void display() { ... } } 所有常量行为一致(极少使用)
​常量级实现 SPRING { @Override void display() { ... } } 不同常量需差异化行为(主流用法)

5 注意事项

①枚举类必须实现接口中的全部抽象方法,否则编译报错
②Java 8+的接口默认方法可被枚举类继承或重写
③常量级实现也可以加入枚举项独有的方法

实例:

public interface Play {
    void play();  // 抽象方法声明
    default void look() {
        System.out.println("看 四季变化~");
    }
}
public enum Season implements Play{
    SPRING("春季", "春暖花开") {
        @Override
        public void play() {
            System.out.println("春天要来了");
        }
        //枚举对象独有的方法show()
        public void show() {
            System.out.println("春天是春暖花开的季节");
        }
    },
    SUMMER("夏季", "炎炎夏日") {
        @Override
        public void play() {
            System.out.println("夏天要来了");
        }
        
        @Override
        public void look() {
            System.out.println("看 知了叫了~");
        }
    };

    //枚举类成员属性用private修饰,防止外部直接修改,对外只提供get方法读取(虽然用其他修饰符也不会报错)
    private String name;
    private String desc;

    Season() {

    }

    Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return name;
    }
}

示例中接口look()方法不用强制实现,但可以重写该方法

枚举类中有抽象方法时,常量项可以实现抽象方法,并不需要在枚举类中声明abstract关键字

public enum Season{
    SPRING("春季", "春暖花开") {
        //实现枚举类中抽象show()方法
        @Override
        public void show() {
            System.out.println("桃花春色暖先开,明媚谁人不看来");
        }
    };

    //枚举类成员属性用private修饰,防止外部直接修改,对外只提供get方法读取(虽然用其他修饰符也不会报错)
    private String name;
    private String desc;

    Season() {

    }

    Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public abstract void show();

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

}

七 其他注意事项

在编程领域,“魔法值”(Magic Number/Value)指在代码中直接出现的、未经解释的数字、字符串或其他字面量常量,其含义难以通过字面理解,需结合上下文推断,如同被施了“魔法”般神秘。需反复追溯上下文才能理解值的含义,增加理解成本(如数字1可能代表“成功”,也可能代表“男性”)

魔法值直接出现在代码逻辑中,缺乏命名或注释说明其含义。

if (status == 2) { ... }  // 数字2的含义不明
alert("操作失败");        // 字符串"操作失败"多次重复出现
setFlag(true);           //true/false缺乏语义
port = 8080;            // 硬编码导致维护成本高

解决方案:
​枚举类(Enum)​:类型安全,避免无效值;可关联附加信息(如描述字段);

八 结束语

枚举是Java献给开发者的类型安全之诗——它以有限的常量,定义无限的行为可能
以简洁的语法,承载严谨的设计哲学
当常量需要智慧,当状态需要秩序,枚举便是答案

掌握枚举,不仅是语法技巧,更是对确定性设计、优雅抽象的不懈追求。在状态、策略与常量的世界里,枚举让代码成为可读的诗,而非混乱的符。

posted @ 2025-06-21 23:45  空菌  阅读(608)  评论(0)    收藏  举报