掌握Java枚举这几个知识点,平常开发就够啦

前言

春节来临之际,祝你们新年快乐。整理了Java枚举的相关知识,算是比较基础的,但愿你们一块儿学习进步。 java

本章节全部代码demo已上传githubgit

1、枚举类型是什么?

JDK5引入了一种新特性,关键字enum能够将一组具名的值的有限集合建立为一种新的类型,而这些具名的值能够做为常规的程序组件使用,这就是枚举类型。github

一个枚举的简单例子编程

enum SeasonEnum {
    SPRING,SUMMER,FALL,WINTER;
}
复制代码

2、 枚举类的经常使用方法

Enum经常使用方法有如下几种:数组

  • name(); 返回enum实例声明时的名字。
  • ordinal(); 返回一个int值,表示enum实例在声明的次序。
  • equals(); 返回布尔值,enum实例判断相等
  • compareTo() 比较enum实例与指定对象的顺序
  • values(); 返回enum实例的数组
  • valueOf(String name) 由名称获取枚举类中定义的常量

直接看例子吧:安全

enum Shrubbery {
    GROUND,CRAWLING, HANGING
}
public class EnumClassTest {
    public static void main(String[] args) {
        //values 返回enum实例的数组
        for (Shrubbery temp : Shrubbery.values()) {
            // name 返回实例enum声明的名字
            System.out.println(temp.name() + " ordinal is " + temp.ordinal() + " ,equal result is " +
                    Shrubbery.CRAWLING.equals(temp) + ",compare result is " + Shrubbery.CRAWLING.compareTo(temp));
        }
        //由名称获取枚举类中定义的常量值
        System.out.println(Shrubbery.valueOf("CRAWLING"));
    }
}

复制代码

运行结果:bash

GROUND ordinal is 0 ,equal result is false,compare result is 1
CRAWLING ordinal is 1 ,equal result is true,compare result is 0
HANGING ordinal is 2 ,equal result is false,compare result is -1
CRAWLING
复制代码

3、枚举类的真面目

枚举类型究竟是什么类呢?咱们新建一个简单枚举例子,看看它的庐山真面目。以下:ide

public enum Shrubbery {
    GROUND,CRAWLING, HANGING
}
复制代码

使用javac编译上面的枚举类,可得Shrubbery.class文件。post

javac Shrubbery.java
复制代码

再用javap命令,反编译获得字节码文件。如:执行javap Shrubbery.class可到如下字节码文件。性能

Compiled from "Shrubbery.java"
public final class enumtest.Shrubbery extends java.lang.Enum<enumtest.Shrubbery> {
  public static final enumtest.Shrubbery GROUND;
  public static final enumtest.Shrubbery CRAWLING;
  public static final enumtest.Shrubbery HANGING;
  public static enumtest.Shrubbery[] values();
  public static enumtest.Shrubbery valueOf(java.lang.String);
  static {};
}
复制代码

从字节码文件能够发现:

  • Shrubbery枚举变成了一个final修饰的类,也就是说,它不能被继承啦。
  • Shrubbery是java.lang.Enum的子类。
  • Shrubbery定义的枚举值都是public static final修饰的,即都是静态常量。

为了看得更仔细,javap反编译加多个参数-c,执行以下命令:

javap -c Shrubbery.class
复制代码

静态代码块的字节码文件以下:

static {};
    Code:
       0: new           #4 // class enumtest/Shrubbery
       3: dup
       4: ldc           #7 // String GROUND
       6: iconst_0
       7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #9 // Field GROUND:Lenumtest/Shrubbery;
      13: new           #4 // class enumtest/Shrubbery
      16: dup
      17: ldc           #10 // String CRAWLING
      19: iconst_1
      20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #11 // Field CRAWLING:Lenumtest/Shrubbery;
      26: new           #4 // class enumtest/Shrubbery
      29: dup
      30: ldc           #12 // String HANGING
      32: iconst_2
      33: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic     #13 // Field HANGING:Lenumtest/Shrubbery;
      39: iconst_3
      40: anewarray     #4 // class enumtest/Shrubbery
      43: dup
      44: iconst_0
      45: getstatic     #9 // Field GROUND:Lenumtest/Shrubbery;
      48: aastore
      49: dup
      50: iconst_1
      51: getstatic     #11 // Field CRAWLING:Lenumtest/Shrubbery;
      54: aastore
      55: dup
      56: iconst_2
      57: getstatic     #13 // Field HANGING:Lenumtest/Shrubbery;
      60: aastore
      61: putstatic     #1 // Field $VALUES:[Lenumtest/Shrubbery;
      64: return
}
复制代码
  • 0-39行实例化了Shrubbery枚举类的GROUND,CRAWLING, HANGING;
  • 40-64为建立Shrubbery[]数组$VALUES,并将上面的三个实例化对象放入数组的操做。
  • 所以,枚举类方法values()返回enum枚举实例的数组是否是豁然开朗啦。

4、枚举类的优势

枚举类有什么优势呢?就是咱们为何要选择使用枚举类呢?由于它能够加强代码的可读性,可维护性,同时,它也具备安全性。

枚举类能够加强可读性、可维护性

假设如今有这样的业务场景:订单完成后,通知买家评论。很容易有如下代码:

//订单已完成
if(3==orderStatus){
//do something    
}
复制代码

很显然,这段代码出现了魔法数,若是你没写注释,谁知道3表示订单什么状态呢,不只阅读起来比较困难,维护起来也很蛋疼?若是使用枚举类呢,以下:

public enum OrderStatusEnum {
    UNPAID(0, "未付款"), PAID(1, "已付款"), SEND(2, "已发货"), FINISH(3, "已完成"),;

    private int index;

    private String desc;

    public int getIndex() {
        return index;
    }

    public String getDesc() {
        return desc;
    }

    OrderStatusEnum(int index, String desc) {
        this.index = index;
        this.desc = desc;
    }
}

 //订单已完成
 if(OrderStatusEnum.FINISH.getIndex()==orderStatus){
  //do something
 }
复制代码

可见,枚举类让这段代码可读性更强,也比较好维护,后面加个新的订单状态,直接添加多一种枚举状态就能够了。有些朋友认为,public static final int这种静态常量也能够实现该功能呀,以下:

public class OrderStatus {
    //未付款
    public static final int UNPAID = 0;
    public static final int PAID = 1;
    public static final int SENDED = 2;
    public static final int FINISH = 3;
    
}

 //订单已完成
 if(OrderStatus.FINISH==orderStatus){
     //do something
 }
复制代码

固然,静态常量这种方式实现,可读性是没有任何问题的,平常工做中代码这样写也无可厚非。可是,定义int值相同的变量,容易混淆,如你定义PAIDSENDED状态都是2,编译器是不会报错的。

所以,枚举类第一个优势就是可读性,可维护性都不错,因此推荐。

枚举类安全性

除了可读性、可维护性外,枚举类还有个巨大的优势,就是安全性。

从上一节枚举类字节码分析,咱们知道:

  • 一个枚举类是被final关键字修饰的,不能被继承。
  • 而且它的变量都是public static final修饰的,都是静态变量。

当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。

5、枚举的常见用法

enum组织常量

在JDK5以前,常量定义都是这样,先定义一个类或者接口,属性类型都是public static final...,有了枚举以后,能够把常量组织到枚举类了,以下:

enum SeasonEnum {
    SPRING,SUMMER,FALL,WINTER,;
}
复制代码

enum与switch 环环相扣

通常来讲,switch-case中只能使用整数值,可是枚举实例天生就具有整数值的次序,所以,在switch语句中是可使用enum的,以下:

enum OrderStatusEnum {
   UNPAID, PAID, SEND, FINISH
}
public class OrderStatusTest {
    public static void main(String[] args) {
        changeByOrderStatus(OrderStatusEnum.FINISH);
    }

    static void changeByOrderStatus(OrderStatusEnum orderStatusEnum) {
        switch (orderStatusEnum) {
            case UNPAID:
                System.out.println("老板,你下单了,赶忙付钱吧");
                break;
            case PAID:
                System.out.println("我已经付钱啦");
                break;
            case SENDED:
                System.out.println("已发货");
                break;
            case FINISH:
                System.out.println("订单完成啦");
                break;
        }
    }
}

复制代码

在平常开发中,enum与switch一块儿使用,会让你的代码可读性更好哦。

向枚举中添加新的方法

能够向枚举类添加新方法的,如get方法,普通方法等,如下是平常工做最经常使用的一种枚举写法:

public enum OrderStatusEnum {
    UNPAID(0, "未付款"), PAID(1, "已付款"), SENDED(2, "已发货"), FINISH(3, "已完成"),;

    //成员变量
    private int index;
    private String desc;

    //get方法
    public int getIndex() {
        return index;
    }

    public String getDesc() {
        return desc;
    }

    //构造器方法
     OrderStatusEnum(int index, String desc) {
        this.index = index;
        this.desc = desc;
    }

    //普通方法
    public static OrderStatusEnum of(int index){
        for (OrderStatusEnum temp : values()) {
            if (temp.getIndex() == index) {
                return temp;
            }
        }
        return null;
    }
}

复制代码

枚举实现接口

全部枚举类都继承于java.lang.Enum,因此枚举不能再继承其余类了。可是枚举能够实现接口呀,这给枚举增添了很多色彩。以下:

public interface ISeasonBehaviour {

    void showSeasonBeauty();

    String getSeasonName();
}

public enum SeasonEnum implements ISeasonBehaviour {
    SPRING(1,"春天"),SUMMER(2,"夏天"),FALL(3,"秋天"),WINTER(4,"冬天"),
    ;

    private int index;
    private String name;

    SeasonEnum(int index, String name) {
        this.index = index;
        this.name = name;
    }

    public int getIndex() {
        return index;
    }
    public String getName() {
        return name;
    }

    //接口方法
    @Override
    public void showSeasonBeauty() {
        System.out.println("welcome to " + this.name);
    }

    //接口方法
    @Override
    public String getSeasonName() {
        return this.name;
    }
}
复制代码

使用接口组织枚举

public interface Food {
    enum Coffee implements Food{  
        BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO  
    }  
    enum Dessert implements Food{  
        FRUIT, CAKE, GELATO  
    }  
}
复制代码

6、枚举类比较是用==仍是equals?

先看一个例子,以下:

public class EnumTest {
    public static void main(String[] args) {

        Shrubbery s1 = Shrubbery.CRAWLING;
        Shrubbery s2 = Shrubbery.GROUND;
        Shrubbery s3 = Shrubbery.CRAWLING;

        System.out.println("s1==s2,result: " + (s1 == s2));
        System.out.println("s1==s3,result: " + (s1 == s3));
        System.out.println("Shrubbery.CRAWLING.equals(s1),result: "+Shrubbery.CRAWLING.equals(s1));
        System.out.println("Shrubbery.CRAWLING.equals(s2),result: "+Shrubbery.CRAWLING.equals(s2));

    }
}
复制代码

运行结果:

s1==s2,result: false
s1==s3,result: true
Shrubbery.CRAWLING.equals(s1),result: true
Shrubbery.CRAWLING.equals(s2),result: false
复制代码

能够发现无论用==仍是equals,都是能够的。其实枚举的equals方法,就是用==比较的,以下:

public final boolean equals(Object other) {
    return this==other;
}
复制代码

7、枚举实现的单例

effective java提过,最佳的单例实现模式就是枚举模式。单例模式的实现有好几种方式,为何是枚举实现的方式最佳呢?

由于枚举实现的单例有如下优势:

  • 枚举单例写法简单
  • 枚举可解决线程安全问题
  • 枚举可解决反序列化会破坏单例的问题

一个枚举单例demo以下:

public class SingletonEnumTest {
   public enum SingletonEnum {
        INSTANCE,;
        private String name;

        public String getName() {
            return name;
        }

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

    public static void main(String[] args) {
        SingletonEnum.INSTANCE.setName("jay@huaxiao");
        System.out.println(SingletonEnum.INSTANCE.getName());
    }
}
复制代码

有关于枚举实现单例,想深刻了解的朋友能够看Hollis大神这篇文章,写得真心好!为何我墙裂建议你们使用枚举来实现单例。

8、EnumSet 和EnumMap

EnumSet

先来看看EnumSet的继承体系图

显然,EnumSet也实现了set接口,相比于HashSet,它有如下优势:

  • 消耗较少的内存
  • 效率更高,由于是位向量实现的。
  • 能够预测的遍历顺序(enum常量的声明顺序)
  • 拒绝加null

EnumSet就是set的高性能实现,它的要求就是存放必须是同一枚举类型。 EnumSet的经常使用方法:

  • allof() 建立一个包含指定枚举类里全部枚举值的EnumSet集合
  • range() 获取某个范围的枚举实例
  • of() 建立一个包括参数中全部枚举元素的EnumSet集合
  • complementOf() 初始枚举集合包括指定枚举集合的补集

看实例,最实际:

public class EnumTest {
    public static void main(String[] args) {
        // Creating a set
        EnumSet<SeasonEnum> set1, set2, set3, set4;

        // Adding elements
        set1 = EnumSet.of(SeasonEnum.SPRING,  SeasonEnum.FALL, SeasonEnum.WINTER);
        set2 = EnumSet.complementOf(set1);
        set3 = EnumSet.allOf(SeasonEnum.class);
        set4 = EnumSet.range(SeasonEnum.SUMMER,SeasonEnum.WINTER);
        System.out.println("Set 1: " + set1);
        System.out.println("Set 2: " + set2);
        System.out.println("Set 3: " + set3);
        System.out.println("Set 4: " + set4);
    }
}
复制代码

输出结果:

Set 1: [SPRING, FALL, WINTER]
Set 2: [SUMMER]
Set 3: [SPRING, SUMMER, FALL, WINTER]
Set 4: [SUMMER, FALL, WINTER]
复制代码

EnumMap

EnumMap的继承体系图以下:

EnumMap也实现了Map接口,相对于HashMap,它也有这些优势:

  • 消耗较少的内存
  • 效率更高
  • 能够预测的遍历顺序
  • 拒绝null

EnumMap就是map的高性能实现。 它的经常使用方法跟HashMap是一致的,惟一约束是枚举相关。

看实例,最实际:

public class EnumTest {
    public static void main(String[] args) {
        Map<SeasonEnum, String> map = new EnumMap<>(SeasonEnum.class);
        map.put(SeasonEnum.SPRING, "春天");
        map.put(SeasonEnum.SUMMER, "夏天");
        map.put(SeasonEnum.FALL, "秋天");
        map.put(SeasonEnum.WINTER, "冬天");
        System.out.println(map);
        System.out.println(map.get(SeasonEnum.SPRING));
    }
}
复制代码

运行结果

{SPRING=春天, SUMMER=夏天, FALL=秋天, WINTER=冬天}
春天
复制代码

9、待更新

有关于枚举关键知识点,亲爱的朋友,你有没有要补充的呢?

参考与感谢

我的公众号

  • 若是你是个爱学习的好孩子,能够关注我公众号,一块儿学习讨论。
  • 若是你以为本文有哪些不正确的地方,能够评论,也能够关注我公众号,私聊我,你们一块儿学习进步哈。
相关文章
相关标签/搜索