所有知识体系文章,GitHub已收录,欢迎Star!再次感谢,愿你早日进入大厂!

GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual

深入理解Java枚举


一、什么是枚举

1.1 什么是枚举?

1.2 Java中的枚举类

二、Java枚举的语法

枚举类中的声明

1访问修辞符 enum 枚举名 {
2    枚举成员,
3    枚举成员,
4    ...
5};

class类中枚举的声明

1访问修饰符 class 类名 {
2    enum 枚举名 {
3        枚举成员,
4        枚举成员,
5        ...
6    }
7}

三、Java枚举类的使用规则和应用场景

3.1 Java枚举类的使用规则

3.2 Java枚举类的应用场景

四、枚举类的基本使用步骤解析

那我们就解释以下这两个规则,我们在上述中已经了解了枚举的作用。Java中枚举也不例外,也是一一列举出来方便我们拿出来一个或多个使用。这有点像我们的多选框,我们把需要用到的所有选项内容放在各个多选框后面,当我们在使用的时候只需要勾选自己需要的勾选框即可,这就代表了我们需要被选中多选框后面的内容。

那么,Java中的枚举类是如何使用呢?

这里我们简单的模拟一个场景,假设你的女朋友十分的喜欢喝点冷饮或热奶茶之类的饮品,在生活中也有很多像蜜雪冰城等等这种类型的饮品店。当你为女朋友买她爱喝的珍珠奶茶时,服务员会问你,要大杯、中杯还是小杯的。当然,为了满足女朋友,你通常会选择大杯。这就意味着店内不允许顾客点规则外的饮品。

注意: 如果你是初学者或是不了解枚举类的使用,此基本使用不懂没有关系,请继续往下看即可!

于是,我用Java代码来实现一下,上述场景。

首先,创建枚举类。分别为珍珠奶茶添加大、中、小杯杯型。

 1package com.mylifes1110.java;
2
3/**
4 * @ClassName PearlMilkTea
5 * @Description 为珍珠奶茶添加三个杯型:大、中、小
6 * @Author Ziph
7 * @Date 2020/6/8
8 * @Since 1.8
9 */

10public enum PearlMilkTea {
11    //注意:这里枚举类中只有枚举成员,我在此省略了;结束符
12    SMALL, MEDIUM, LARGE
13}

其次,创建珍珠奶茶对象,再有方法来判断枚举类中的大、中、小杯。最后打印女朋友喝哪个杯型的珍珠奶茶!

 1package com.mylifes1110.test;
2
3import com.mylifes1110.java.PearlMilkTea;
4
5/**
6 * @ClassName PearlMilkTeaTest
7 * @Description 为女朋友买哪个杯型的珍珠奶茶(默认大杯)
8 * @Author Ziph
9 * @Date 2020/6/8
10 * @Since 1.8
11 */

12public class PearlMilkTeaTest {
13    public static void main(String[] args) {
14        //创建大杯的珍珠奶茶对象
15        PearlMilkTea pearlMilkTea = PearlMilkTea.LARGE;
16        PearlMilkTeaTest.drinkSize(pearlMilkTea);
17    }
18
19    //判断为女朋友买哪个杯型的珍珠奶茶
20    public static void drinkSize(PearlMilkTea pearlMilkTea) {
21        if (pearlMilkTea == PearlMilkTea.LARGE) {
22            System.out.println("我为女朋友买了一大杯珍珠奶茶!");
23        } else if (pearlMilkTea == PearlMilkTea.MEDIUM) {
24            System.out.println("我为女朋友买了一中杯珍珠奶茶!");
25        } else {
26            System.out.println("我为女朋友买了一小杯珍珠奶茶!");
27        }
28    }
29}

虽然,我们了解了枚举类中的基本使用,但是我们在语法中还介绍了一种在类中定义的枚举。正好,在此也演示一下。如下:

1public class PearlMilkTea {
2    enum DrinkSize {
3        SMALL,
4        MEDIUM, 
5        LARGE
6    }
7}

如果这样创建就可以在class类中去创建enum枚举类了。想想前面例子中的代码其实并不合理,这是为什么呢?因为我们写代码要遵循单一职责原则和见命知意的命名规范。所以,我写的代码是在珍珠奶茶的枚举类中列举的大、中、小的三种杯型枚举成员。所以根据规范来讲,我们珍珠奶茶中不能拥有杯型相关的枚举,毕竟我们在生活中的这类饮品店中喝的所有饮品种类都有这三种杯型,因此我们的所有饮品种类中都需要写一个枚举类,显然这是很不合理的。

如果让它变的更加合理化,我们就细分饮品种类来创建饮品枚举类和杯型的枚举类并分别两两适用即可。也许有小伙伴会问我为什么我要说这些合理不合理呢?因为自我感觉这是对枚举类应用的思想铺垫,所以你品、你细品!

五、自定义枚举类

5.1 自定义枚举类步骤

既然,上述第三章我举出了这么多枚举类的应用场景,那我们挑选一个比较经典的春夏秋冬来实现自定义枚举类。

首先,我们先创建一个季节类,分别提供属性、私有构造器、春夏秋冬常量、Getter方法和toString方法,步骤如下:

 1package com.mylifes1110.java;
2
3/**
4 * 自定义季节的枚举类
5 */

6public class Season {
7    //声明Season对象的属性,为private final修饰
8    private final String seasonName;
9
10    //私有化构造器,并为对象赋值
11    private Season(String seasonName) {
12        this.seasonName = seasonName;
13    }
14
15    //提供当前枚举的多个对象,为public static final修饰
16    public static final Season SPRING = new Season("春天");
17    public static final Season SUMMER = new Season("夏天");
18    public static final Season AUTUMN = new Season("秋天");
19    public static final Season WINTER = new Season("冬天");
20
21    //提供外界通过getter方法来获取枚举对象的属性
22    public String getSeasonName() {
23        return seasonName;
24    }
25
26    //重写toString方法,以便打印出枚举结果
27    @Override
28    public String toString() {
29        return "Season{" +
30                "seasonName='" + seasonName + '\'' +
31                '}';
32    }
33}

其次,我们去创建一个测试类,来使用该自定义枚举类创建对象。由此看来,我们就可以根据类名来句点出常量对象了!

 1package com.mylifes1110.test;
2
3import com.mylifes1110.java.Season;
4
5/**
6 * 测试类
7 */

8public class SeasonTest {
9    public static void main(String[] args) {
10        Season spring = Season.SPRING;
11        System.out.println(spring);
12    }
13}

最后打印结果是春天的对象,由于我们覆盖了toString方法,即可见对象内的内容。

5.2 使用带有参枚举类

在这里我将自定义枚举类改装了一下,改装成了enum枚举类实现的使用有参对象。如下:

 1package com.mylifes1110.java;
2
3public enum Season {
4    SPRING("春天"),
5    SUMMER("夏天"),
6    AUTUMN("秋天"),
7    WINTER("冬天");
8
9    private final String seasonName;
10
11    Season1(String seasonName) {
12        this.seasonName = seasonName;
13    }
14
15    public String getSeasonName() {
16        return seasonName;
17    }
18}

不知道你有没有发现少了点什么,少的部分其实就是我们创建常量对象的部分,而且在这个枚举类中我也没有去重写toString方法,至于为什么,下面就告诉你。

注意: 枚举对象之间用,隔开!

其次,去创建了该枚举类的测试类,我们测试以下,并看一下没有重写toString方法打印出来的结果。

 1package com.mylifes1110.test;
2
3import com.mylifes1110.java.Season;
4
5public class Seaso1Test {
6    public static void main(String[] args) {
7        Season1 spring = Season.SPRING;
8        System.out.println(spring);                     //SPRING
9        System.out.println(spring.getSeasonName());     //春天
10    }
11}

这里我将打印的结果放在了打印语句后面的注释中。我们发现没有重写toString方法竟然打印出来的是SPRING,这是为什么呢?这应该从我们的继承关系中分析,如果继承的是基类Object的话,没有重写toString方法会打印对象地址。那么我们就可以断定,enum枚举类的父类不是Object。那它的父类是谁呢?我们可以借助来对象来获取其父类,如下:

1System.out.println(Season.class.getSuperclass());        //class java.lang.Enum

同样,答案放在了代码后面的注释中。我们发现它默认继承的是Enum类。那么,我们稍后就来就看看这个类中到底写了些什么方法。

六、Enum常用方法的使用

6.1 Enum中的所有方法

6.2 name和toString

1System.out.println(Season.SUMMER.name());            //SUMMER
2System.out.println(Season.SUMMER.toString());        //SUMMER

6.3 valueOf

1System.out.println(Season.valueOf("WINTER"));            //WINTER
2System.out.println(Season.valueOf("WIN"));                //java.lang.IllegalArgumentException

6.4 values

1Season[] seasons = Season.values();
2for (Season season : seasons) {
3    System.out.print(season + " ");
4}

结果为:

1SPRING SUMMER AUTUMN WINTER 

6.5 ordinal

1//获取指定枚举成员的次序
2System.out.println(Season.SUMMER.ordinal());
3
4//获取所有成员的次序
5Season[] seasons = Season.values();
6for (Season s : seasons) {
7    System.out.println(s + " -> " + s.ordinal());
8}

结果为:

其源码就是返回了一个从0开始int类型的值,从源码中也可以看出最大值是int取值范围的最大值。如下:

6.6 compareTo

首先,我们要知道SUMMER的次序数为1,WINTER的次序数为3。当使用前者比较后者,打印的结果是前者与后者相减后的差值,即1-3=-2

1System.out.println(Season.SUMMER.compareTo(Season.WINTER));            //-2

它的源码是怎么做的呢?那我们进入查看一下。

其中,前面的操作都是在判断比较的双方是否是一个枚举类,如果不是的话就抛出异常。如果为枚举类的话,就直接将次序数做了相减操作并返回。

七、Java枚举的高级特性

7.1 常量

示例:

1public enum Season {
2    SPRING, SUMMER, AUTUMN, WINTER
3}

7.2 switch语句

 1package com.mylifes1110.java;
2
3public class WeekTest {
4    public static void main(String[] args) {
5        Week week = Week.MONDAY;
6        switch (week) {
7            case MONDAY:
8                System.out.println("星期一");
9                break;
10            case TUESDAY:
11                System.out.println("星期二");
12                break;
13            case WEDNESDAY:
14                System.out.println("星期三");
15                break;
16            case THURSDAY:
17                System.out.println("星期四");
18                break;
19            case FRIDAY:
20                System.out.println("星期五");
21                break;
22            case SATURDAY:
23                System.out.println("星期六");
24                break;
25            case SUNDAY:
26                System.out.println("星期日");
27                break;
28            default:
29                System.out.println("null");
30        }
31    }
32}
33
34enum Week {
35    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
36}

7.3 枚举中定义多个参数与方法

 1package com.mylifes1110.java;
2
3public enum Season {
4    SPRING("春天"),
5    SUMMER("夏天"),
6    AUTUMN("秋天"),
7    WINTER("冬天");
8
9    private final String seasonName;
10
11    public static String getName(int index) {  
12        for (Season s : Season.values()) {  
13            if (c.getIndex() == index) {  
14                return c.name;  
15            }  
16        }  
17        return null;  
18    }
19
20    Season1(String seasonName) {
21        this.seasonName = seasonName;
22    }
23
24    public String getSeasonName() {
25        return seasonName;
26    }
27}

7.4 枚举类实现接口

首先,创建一个接口。

1package com.mylifes1110.inter;
2
3public interface Show {
4    void show();
5}

其次,让我们的四季枚举类实现该接口并重写方法。

 1package com.mylifes1110.java;
2
3import com.mylifes1110.inter.Show;
4
5public enum Season implements Show {
6    SPRING("春天"),
7    SUMMER("夏天"),
8    AUTUMN("秋天"),
9    WINTER("冬天");
10
11    private final String seasonName;
12
13    Season1(String seasonName) {
14        this.seasonName = seasonName;
15    }
16
17    public String getSeasonName() {
18        return seasonName;
19    }
20
21    @Override
22    public void show() {
23        System.out.println("向往四季如春");
24    }
25}

最后,当我们使用每一个枚举类都可以调用show方法,而打印的结果也都是“向往四季如春”

1Season.WINTER.show();                //向往四季如春

聪明的你我相信发现了这个缺点,我们不管使用哪一个枚举成员时,调用的show方法都是同一个。所以,我们在实现接口后,可以这样重写方法,如下:

 1package com.mylifes1110.java;
2
3import com.mylifes1110.inter.Show;
4
5public enum Season1 implements Show {
6    SPRING("春天") {
7        @Override
8        public void show() {
9            System.out.println("春天是个踏青的季节");
10        }
11    },
12    SUMMER("夏天") {
13        @Override
14        public void show() {
15            System.out.println("夏天是个炎热的季节,我要吃冰棍");
16        }
17    },
18    AUTUMN("秋天") {
19        @Override
20        public void show() {
21            System.out.println("秋天还算是凉爽");
22        }
23    },
24    WINTER("冬天") {
25        @Override
26        public void show() {
27            System.out.println("冬天的雪还不错,就是有点冷");
28        }
29    };
30
31    private final String seasonName;
32
33    Season1(String seasonName) {
34        this.seasonName = seasonName;
35    }
36
37    public String getSeasonName() {
38        return seasonName;
39    }
40}

我们在枚举成员的后面加了{},而重写的方法可以写在各个枚举成员中,这样就接触了上述所有的那个限制。这下,我们使用哪个枚举成员对象调用show方法都是不同的。是不是非常NICE?

7.5 使用接口对枚举分类

 1package com.mylifes1110.inter;
2
3public interface Weeks {
4    //工作日
5    enum WorkingDay implements Weeks {
6        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
7    }
8
9    //双休日
10    enum Weekend implements Weeks {
11        SATURDAY, SUNDAY
12    }
13}
14
15class WeeksTest {
16    public static void main(String[] args) {
17        System.out.print("双休日:");
18        for (Weeks.Weekend weekend : Weeks.Weekend.values()) {
19            System.out.print(weekend + " ");        //双休日:SATURDAY SUNDAY
20        }
21
22        //换行
23        System.out.println();
24
25        System.out.print("工作日:");
26        for (Weeks.WorkingDay workingDay : Weeks.WorkingDay.values()) {
27            System.out.print(workingDay + " ");     //工作日:MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY
28        }
29
30        //换行
31        System.out.println();
32
33        Weeks.WorkingDay friday = Weeks.WorkingDay.FRIDAY;
34        System.out.println("星期五:" + friday);      //星期五:FRIDAY
35    }
36}

八 枚举类集合

8.1 EnumSet集合

8.1.1 allOf
1//创建一个包含Week所有枚举元素的Set集合
2EnumSet<Week> weeks = EnumSet.allOf(Week.class);
3System.out.println(weeks);              //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
4
5//打印Set集合中的元素
6for (Week week1 : weeks) {
7    System.out.print(week1 + " ");      //MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY
8}
8.1.2 clone
1//返回一个Set集合
2System.out.println(weeks.clone());      //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
8.1.3 range
1//创建一个最初包含由两个指定端点所定义范围内的所有元素的枚举 set。
2System.out.println(EnumSet.range(Week.MONDAY, Week.FRIDAY));        //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
8.1.4 complementOf
1//创建一个其元素类型与指定枚举set相同的set集合(新集合中包含原集合所不包含的枚举成员)
2EnumSet<Week> weeks1 = EnumSet.complementOf(weeks);
3System.out.println(weeks1);             //[]
4
5EnumSet<Week> range = EnumSet.range(Week.MONDAY, Week.FRIDAY);
6EnumSet<Week> weeks3 = EnumSet.complementOf(range);
7System.out.println(weeks3);                //[SATURDAY, SUNDAY]
8.1.5 copyOf
1//创建一个其元素类型与指定枚举 set 相同的枚举 set集合(新集合中包含与原集合相同的枚举成员)
2EnumSet<Week> weeks2 = EnumSet.copyOf(weeks);
3System.out.println(weeks2);             //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
1//复制存储枚举成员的HashSet集合
2Set set = new HashSet();
3set.add(Week.MONDAY);
4EnumSet set1 = EnumSet.copyOf(set);
5System.out.println(set1);        //[MONDAY]
8.1.6 of
1//创建一个最初包含指定元素的枚举 set。
2System.out.println(EnumSet.of(Week.MONDAY,Week.FRIDAY));            //[MONDAY, FRIDAY]
8.1.7 noneOf
1EnumSet<Week> noneOf = EnumSet.noneOf(Week.class);
2System.out.println(noneOf);                     //[]

8.2 EnumMap集合

8.2.1 EnumMap集合的方法列表

关于EnumMap集合的使用与HashMap是一致的,没有什么特殊的。至于EnumMap集合的方法,我这里列举一下。

8.2.2 EnumMap集合的基本使用

EnumMap集合是我们new出来的对象,创建出来的对象需要传入一个枚举的类对象,才返回一个Map集合。Map集合是键值对形式存储,所以我们在写EnumMap集合的泛型时,根据需求来写,如果需要键是某枚举类型,我们泛型就写它。如果有枚举类是值的要求,那就泛型中的值写枚举类。键值对都要求是枚举那也是OK的,我们写泛型时都写需求的枚举类即可。除了创建对象和存储对象需要指定枚举类外,其他的与HashMap基本相同。

如下,我在创建EnumMap集合时执行的Week枚举类的类对象,泛型的键写的是Week枚举类,值写的Integer,这就意味着我们在put(存储键值对)的时候,键需要存储Week枚举类中的枚举成员,值需要存储Integer数值。

1EnumMap<Week, Integer> map = new EnumMap<>(Week.class);
2map.put(Week.MONDAY, 1);
3map.put(Week.THURSDAY, 4);
4System.out.println(map);            //{MONDAY=1, THURSDAY=4}
06-09 09:16