从零开始学Java之枚举类是怎么回事?

作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

我们在开发时,有时会遇到列举、请点或计数的情况,比如我们想匹配性别里的"男"、"女",或者列举今天是周一、周二、周三......这种表示"有限个数"的情况,我们该怎么实现呢?根据现在的知识储备,我们可以使用以下技术:

  • 使用常量:使用final关键字定义public static final常量,这些常量值可以被其他类直接引用,但这种方式不能保证唯一性。
  • 使用接口:使用接口来定义有限的个数,但需要在实现类中逐个实现,较为繁琐。

但是以上技术都存在一些缺陷,所以今天壹哥就通过一篇文章,给大家介绍一个新的小知识--枚举类!通过枚举,实现以上效果会更方便。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【4500】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear...

Gitee: gitee.com/sunyiyi/Lea...

一. 枚举简介

1. 概念

首先我们来了解一下"枚举 "这个词的含义。"枚举"是列举、清点或计数的意思,而在计算机里面,枚举通常是表示有限个数的常量集合,例如颜色、性别、星期几、月份等。

在Java中,枚举(enum)其实是一个特殊的类,它是一种表示有限个数的常量集合。枚举类是在Java 5中引入的新特性,它给我们提供了一种更加规范和安全的方式来定义常量。

2. 特点

枚举作为一个类,既符合一般类的特性,也具有自身的特点,如下:

  1. 枚举是有限个数的常量集合;
  2. 枚举类只能使用 public 或 default 访问修饰符,不能使用 private 或 protected修饰符;
  3. 枚举类的构造方法必须是 private 或 default 修饰,不能使用 public 或 protected;
  4. 枚举类默认是按照单例模式进行的实现,枚举常量只会被创建一次,可以保证该值的唯一性;
  5. 枚举类也可以实现接口,实现与普通类相同的功能。

3. 枚举类与普通类的区别

但枚举作为一种特殊的类,本身具有一定的特殊性,它与普通类之间具有如下区别:

  1. 枚举类只能使用 public 或 default 访问修饰符,不能使用 private 或 protected,因为枚举的常量要在其他类中访问;
  2. 枚举类的构造方法必须是 private 或 default 访问修饰符,不能使用 public 或 protected,因为枚举常量不能通过 new 关键字创建;
  3. 枚举类遵循单例模式,枚举常量只会被创建一次,因此可以保证唯一性。

4. 使用场景

既然枚举这么特殊,我们又该在哪些场景下使用呢?大家可以看看壹哥列举出的以下几种场景:

  • 可以表示有限的、预定义的值集合,如一周的星期、一年的季节、性别等;
  • 可以作为方法的参数或返回值类型,使代码更加清晰和类型安全;
  • 可以作为单例模式的实现方式,以保证枚举常量的唯一性;
  • 可以作为工厂模式的实现方式,以根据不同的枚举常量返回不同的实例对象;
  • 可以作为状态机的实现方式,以简化状态转换过程的代码实现。

了解了枚举的基本情况之后,我们就来看看枚举该怎么使用吧。

二. 基本使用

1. 基本语法

我们在声明枚举时必须使用 enum关键字,然后可以定义枚举的名称、可访问性、基础类型和成员等。一个简单的枚举声明语法如下:

java 复制代码
修饰符 enum 枚举名称:enum-base {
    enum-body,...
}

修饰符主要包括 public、private 和 internal;enum-base表示基础类型;enum-body表示枚举中的成员,它是枚举类型中的常数。大家要注意,任意两个枚举成员的名称都不能相同,且它的常数值必须在该枚举的基础类型范围内,多个枚举成员之间要使用逗号分隔。

2. 基本案例

接下来壹哥就根据枚举的语法,给大家定义一个最简单的枚举类。定义枚举类时需要使用enum关键字,且枚举类中的常量应该全部大写,多个枚举值之间用逗号隔开,代码如下所示:

java 复制代码
enum Sex {
    MALE, FAMALE
}

这样我们就定义出了一个表示性别的枚举类,该类中有"MALE"和"FAMALE"两个值,分别表示"男"和"女",其实这两个值也各自对应一个索引序号,分别是"0"和"1"。

3. 基本使用

定义好上面的这个枚举类之后,我们该怎么使用这个类呢?情况下面的代码。

java 复制代码
/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public class Demo01 {
    public static void main(String[] args) {
        // 使用枚举值
        Sex sex = Sex.MALE;
        System.out.println("sex=" + sex);

        // 也可以在switch中使用
        switch (sex) {
            case MALE: {
                System.out.println("性别是男的..." );
                break;
            }
            case FAMALE: {
                System.out.println("性别是女的..." );
                break;
            }
            default:
                throw new IllegalArgumentException("未知性别: " + sex);
        }
    }
}

这个案例中,壹哥给大家演示了如何调用定义好的枚举值,尤其是在switch语句中,枚举更是经常与其配合使用。

4. 定义方法

既然枚举是一个类,那么在枚举类也可以拥有方法,我们可以在枚举类中定义构造方法和普通的方法。如果是构造方法,则必须使用private私有的或default默认的修饰符,其他的普通方法则需要使用public或default来修饰。如下所示:

java 复制代码
/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public enum Size {
    //3个枚举值
    SMALL("S"), MEDIUM("M"), LARGE("L");
    private String abbreviation;

    //构造方法必须私有化或默认的,否则会出现编译错误:
    //llegal modifier for the enum constructor; only private is permitted.
    private Size(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }
}

在这个案例中,壹哥定义了一个Size枚举类,该类拥有SMALL("S")、MEDIUM("M")、LARGE("L")三个枚举值,还有一个abbreviation属性和一个getAbbreviation()方法,以及一个私有的构造方法。大家要注意,构造方法必须私有化或默认,否则会出错,"llegal modifier for the enum constructor; only private is permitted"。

5. 实现单例模式

因为枚举的本质也是一种类,并且枚举的常量是在类加载时被创建的,枚举常量只会被创建一次,因此枚举值具有唯一性。所以我们可以根据这种特性,利用枚举来实现单例模式。其实使用枚举来实现单例模式非常简单,我们只需要定义一个枚举类型,然后在该枚举类型中定义一个枚举常量即可。下面是使用枚举来实现单例模式的代码示例:

java 复制代码
/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public enum Singleton {
    //枚举实例值
    INSTANCE;
    //定义其他的属性和方法
    private String name;
	
    public String getName(){
        return name;
    }
    
    public void setName(String name){
        this.name = name;
    }
}

与普通类实现的单例模式相比,用枚举实现的单例模式代码非常简洁。另外我们也可以像普通类一样,在enum类中添加变量和方法。我们可以直接使用Singleton.INSTANCE来获取单例模式的唯一对象,这样就避免了调用公开的getInstance()方法。并且这种枚举单例的写法,可以使得我们完全不用考虑序列化和反射的问题。

6. 实现工厂模式

枚举类还可以作为工厂模式的一种实现方式。工厂模式是一种创建型的设计模式,它给我们提供了一种将对象的创建过程封装起来的方式。接下来壹哥给大家定义一个枚举类来表示不同的图形:

java 复制代码
/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public enum ShapeType {
    //3个枚举值
    CIRCLE,
    RECTANGLE,
    SQUARE
}

然后,我们可以定义一个接口Shape来表示图形:

java 复制代码
/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public interface Shape {
    //绘图的方法
    void draw();
}

接着,我们可以定义不同的类来实现Shape接口,表示不同的图形:

java 复制代码
/**
 * @author 一一哥Sun
 * @company 千锋教育
 * 圆形
 */
class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a 圆形...");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }
}

class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a 矩形...");
    }
}

最后,我们可以定义一个工厂类ShapeFactory,根据不同的枚举类型来创建不同的图形:

java 复制代码
/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public class ShapeFactory {
    public static Shape createShape(ShapeType shapeType) {
        //使用枚举,创建工厂模式
        switch (shapeType) {
            case CIRCLE:
                return new Circle();
            case RECTANGLE:
                return new Rectangle();
            case SQUARE:
                return new Square();
            default:
                throw new IllegalArgumentException("Invalid shape type: " + shapeType);
        }
    }
	
    public static void main(String[] args) {
        // 使用枚举值,调用工厂方法
        Shape shape = ShapeFactory.createShape(ShapeType.CIRCLE);
        shape.draw();
    }
}

这样我们就通过枚举类作为工厂模式的一种实现方式,从而实现了将对象的创建过程封装起来的目的。

三. Enum类

1. 简介

Enum类是Java中的一个特殊类型,是所有枚举类的父类,用于定义枚举类型。但我们在Java中定义枚举类型时,一般都是通过enum关键字来定义。

2. Enum中的方法

Enum类给我们提供了许多方法来支持枚举类型的操作,下面是一些常用的Enum类方法:

  • values():返回枚举类型中的所有常量;
  • valueOf(String name):根据常量的名称返回该常量;
  • name():返回枚举常量的名称;
  • ordinal():返回枚举常量在枚举类型中的顺序;
  • compareTo(E o):比较枚举常量在枚举类型中的顺序;
  • getDeclaringClass():返回枚举常量所属的枚举类型;
  • toString():返回枚举常量的名称。

以上这些方法都是在Enum类中定义的,因此我们在所有的枚举类型中都可以使用这些方法。其中,values()和valueOf()方法是我们最常用的方法,可以用来获取枚举常量和根据常量的名称获取常量。另外还有一些其他的方法,如name()方法可以返回枚举常量的名称,ordinal()方法可以返回枚举常量在枚举类型中的顺序,compareTo()方法可以比较枚举常量在枚举类型中的顺序,getDeclaringClass()方法可以返回枚举常量所属的枚举类型,toString()方法可以返回枚举常量的名称。

3. Enum实现原理

枚举类型的底层实现原理其实并不复杂!枚举类的底层实现原理和Enum类密切相关。

在Java中,枚举类型实际上是一种特殊的类,它们都是java.lang.Enum类的子类。在枚举类型中,每个常量都是Enum类的实例,因此它们都具有Enum类中定义的方法和属性。当我们定义一个枚举类型时,编译器会自动为其生成一个类,该类继承自Enum类,并且包含枚举类型中定义的所有常量和方法。

在Enum类中给我们提供了一些方法来支持枚举类型,如values()和valueOf()方法。其中,values()方法可以返回枚举类型的所有常量,而valueOf()方法可以根据常量的名称返回该常量。在枚举类型中,这些方法都是自动添加的,因此可以直接使用。

并且在枚举的底层,给我们定义了一个final类型的数组,用于保存枚举常量,每个枚举常量都是该数组中的一个元素。又因为枚举类型的构造方法是私有的,枚举常量只会在类加载的时候被创建一次,这样就保证了枚举常量的唯一性。

总之,枚举类型的底层实现原理和Enum类密不可分,Enum类给我们提供了许多方法来支持枚举类型,同时枚举类型的底层实现原理也是基于Enum类实现的。

四. 枚举扩展类

为了方便我们操作枚举类,java.util包中给我们提供了两个新的类,EnumMap和EnumSet,这两个是关于枚举的集合类,可以方便我们操作多个枚举类。

1. EnumMap

EnumMap是一种特殊的Map,它的键是枚举类型,值可以是任意类型。与普通的Map不同,EnumMap的内部会使用数组来保存键值对,因此在效率上会更高。

EnumMap的使用方法与普通的Map类似,但需要我们注意的是,EnumMap的键必须是同一个枚举类型的枚举常量。例如:

java 复制代码
enum Color {
    RED, GREEN, BLUE
}

enum Size {
    SMALL, MEDIUM, LARGE
}

EnumMap<Color, String> colorMap = new EnumMap<>(Color.class);
colorMap.put(Color.RED, "红色");
colorMap.put(Color.GREEN, "绿色");
colorMap.put(Color.BLUE, "蓝色");

// 错误示范:尝试将不同枚举类型的枚举常量作为键
EnumMap<Size, String> sizeMap = new EnumMap<>(Size.class);
sizeMap.put(Size.SMALL, "小");
sizeMap.put(Color.RED, "红色"); // 编译错误:Type mismatch: cannot convert from EnumMap<Color,String> to EnumMap<Size,String>

在上面的例子中,壹哥创建了一个EnumMap,将颜色映射到字符串。由于颜色是同一个枚举类型的枚举常量,因此可以作为EnumMap的键,但如果尝试将不同枚举类型的枚举常量作为键,就会导致编译错误。

另外EnumMap还给我们提供了一些特有的方法,如entrySet()方法可以返回一个包含Map.Entry对象的Set,这些Map.Entry对象的键是枚举类型的枚举常量,值是EnumMap中相应的值。代码如下:

java 复制代码
EnumMap<Color, String> colorMap = new EnumMap<>(Color.class);
colorMap.put(Color.RED, "红色");
colorMap.put(Color.GREEN, "绿色");
colorMap.put(Color.BLUE, "蓝色");

Set<Map.Entry<Color, String>> entrySet = colorMap.entrySet();
for (Map.Entry<Color, String> entry : entrySet) {
    Color color = entry.getKey();
    String name = entry.getValue();
    System.out.println(color + ":" + name);
}

在上面的例子中,我们可以将EnumMap转换为Set,并使用for循环遍历其中的键值对。

2. EnumSet

EnumSet则是一种特殊的Set,它的元素必须是同一个枚举类型的枚举常量。与普通的Set不同,EnumSet内部会使用一个long类型的位向量来保存元素,因此在效率上会更高。

EnumSet的使用方法与普通的Set类似,但是需要注意的是,EnumSet的元素必须是同一个枚举类型的枚举常量。代码如下:

java 复制代码
import java.util.EnumSet;

enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class EnumSetExample {
    public static void main(String[] args) {
        EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
        EnumSet<Day> weekdays = EnumSet.complementOf(weekend);

        System.out.println("Weekend: " + weekend);
        System.out.println("Weekdays: " + weekdays);
    }
}

在这个例子中,壹哥定义了一个枚举类型Day,用于表示一周中的每一天。然后,我们使用EnumSet来创建两个集合,一个包含周末,一个包含工作日。我们可以使用of()方法来创建一个包含指定元素的EnumSet,使用complementOf()方法来创建一个包含枚举类型中未包含的元素的EnumSet。最终的输出结果为:

java 复制代码
Weekend: [SATURDAY, SUNDAY]
Weekdays: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]

从输出结果可以看出,我们成功地使用EnumSet创建了两个集合,并分别包含了周末和工作日。总之,EnumSet是Java中一种非常有用的集合类型,它给我们提供了一种高效的方式来存储和操作枚举类型的枚举常量。

------------------------------正片已结束,来根事后烟----------------------------

五. 结语

至此,壹哥 就通过今天的这篇文章给大家讲解了枚举相关的内容,不知道你学会了吗?如果你还有别的问题,可以在评论区给壹哥留言哦。

另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

相关推荐
南山十一少19 分钟前
Spring Security+JWT+Redis实现项目级前后端分离认证授权
java·spring·bootstrap
闲猫2 小时前
go orm GORM
开发语言·后端·golang
427724002 小时前
IDEA使用git不提示账号密码登录,而是输入token问题解决
java·git·intellij-idea
丁卯4042 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
chengooooooo2 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
李长渊哦2 小时前
常用的 JVM 参数:配置与优化指南
java·jvm
计算机小白一个2 小时前
蓝桥杯 Java B 组之设计 LRU 缓存
java·算法·蓝桥杯
南宫生5 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长5 小时前
Maven 基础环境搭建与配置(一)
java·maven
bing_1586 小时前
简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用
spring boot·后端·简单工厂模式