一文搞懂枚举类型

目录

前言

一、什么是枚举类型

二、枚举类型的使用

三、使用枚举实现单例模式

总结


前言

  在平时的开发过程中,经常会遇到需要定义一组有限的常量的情况,这时候能使用的方法为:定义一个常量类或者接口,这种方法存在诸多缺陷,比如在类或者接口中定义了多个值为一致的常量,这样就会导致表达的意思混淆,甚至可能出现==来比较两个表示不同意思的常量。同时接口或者类可以被实现和继承并进行修改,通过获取元素的方式也能够修改其中元素的值,是非常不安全的。

  Java 5新增了枚举类型,通过枚举类型可以更好得实现表达一组常量,枚举类型是一个特殊得类类型,但是却比普通的类要更安全、便捷。下面就详细介绍一下枚举类型


一、什么是枚举类型

  枚举类型其实就是一个特殊的类类型,本质上是一种继承java.lang.Enum类,是引用数据类型。下面定义一个枚举类型

public enum MyTestEnum {

    MONDAY(1, "xing qi yi"),
    TUESDAY(2, "xing qi er"),
    WEDNESDAY(3, "xing qi sang"),
    THURSDAY(4, "xing qi si"),
    FRIDAY(5, "xing qi wu"),
    SATURDAY(6, "xing qi liu"),
    SUNDAY(7, "xing qi tian");

    private int code;

    private String desc;

    MyTestEnum(int code, String desc) {
        this.code=code;
        this.desc=desc;
    }
}

枚举类只有一个私有类型的构造方法,所以不能够创建对象。通过javac对该枚举类进行编译,然后通过javap对该类进行反编译,看看结果是什么

可以看到,生成一个继承java.lang.Enum的类,枚举类里面的元素都会生成定义的枚举类类型的对象,生成的类为final修饰,所以不能进行继承,保证了枚举类型的安全。所有枚举内的每一个元素都是一个对象。

二、枚举类型的使用

  枚举类型除了可以列举各种类型以外,还可以使用switch语句定义各个枚举所要执行的操作,例如有这个枚举,定义了加减乘除的符号,并且定了了加减乘除的运算

public enum OperationEnum {

    PLUS,

    MINUS,

    TIMES,

    DIVIDE;

    // 定义加减乘除算法
    double apply(double x, double y) {
        switch (this){
            case PLUS:
                return x+y;
            case MINUS:
                return x-y;
            case TIMES:
                return x*y;
            case DIVIDE:
                return x/y;
        }
        throw new AssertionError("Unknown op" + this);
    }
}

 这样使用起来看起来很方便,但是需要添加新的枚举的时候,如果忘记在case里面新增枚举类型之后就会导致新添加的枚举常量无法匹配到运算公式而抛出错误。

可以通过在枚举类中添加一个抽象方法,这样每当添加一个枚举的时候,都要求去实现这个抽象方法,不然就会编译出错,这样就不会导致忘记添加规则的情况了。

public enum OperationEnum {

    PLUS() {
        @Override
        double apply(double x, double y) {
            return x + y;
        }
    },

    MINUS {
        @Override
        double apply(double x, double y) {
            return x - y;
        }
    },

    TIMES {
        @Override
        double apply(double x, double y) {
            return x * y;
        }
    },

    DIVIDE {
        @Override
        double apply(double x, double y) {
            return x / y;
        }
    };

    // 定义加减乘除算法
    abstract double apply(double x, double y);
}

在枚举类中定义一个抽象方法可以很好的解决这个问题,但是有时候存在多个枚举类型值的计算方式是一样的,如果每个枚举都要去实现这个抽象方法,那么就会导致大量重复的代码。如下面这个枚举

public enum MyTestEnum {

    // 周一至周五工作时间超过8个小时之后,就会按照小时提供加班费。周六周天工作都会按小时提供加班费

    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY;

    public static final int HOURS_PER_SHIFT = 8;

    double pay(double hoursWorked, double payRate) {
        // 基本工资
        double basePay = hoursWorked * payRate;

        // 加班工资
        double overtimePay;
        switch (this) {
            case SATURDAY: case SUNDAY:
                overtimePay=hoursWorked*payRate/2;
            default:
                overtimePay=hoursWorked>HOURS_PER_SHIFT?(hoursWorked-HOURS_PER_SHIFT)*payRate/2:0;
        }
        return basePay + overtimePay;
    }
}

这种情况会switch语句会比抽象方法的方式更加的简洁方便,但是也存在弊端,比如现在需要添加法定节假日的薪资计算方法,此时就需要添加case,如果忘记添加case,则会导致计算薪资错误。这个时候就会联想到java中的抽象,可以跟类一样,将薪资计算的方法提取到一个专门计算薪资的类中,然后根据情况来调用计算薪资类的方法来计算薪资。枚举类型也支持这样的实现,通过在枚举中添加一个私有的嵌套枚举,专门来负责计算薪资的事情。

public enum MyTestEnumV2 {

    // 周一至周五工作时间超过8个小时之后,就会按照小时提供加班费。周六周天工作都会按小时提供加班费

    MONDAY(PayEnum.WEEKDAY), TUESDAY(PayEnum.WEEKDAY), WEDNESDAY(PayEnum.WEEKDAY),
    THURSDAY(PayEnum.WEEKDAY), FRIDAY(PayEnum.WEEKDAY), SATURDAY(PayEnum.WEEKEND),
    SUNDAY(PayEnum.WEEKEND);

    private final PayEnum payEnum;
    MyTestEnumV2(PayEnum payEnum) {
        this.payEnum=payEnum;
    }

    double pay(double hoursWorked, double payRate) {
        return this.payEnum.pay(hoursWorked, payRate);
    }

    // 计算工资的枚举
    private enum PayEnum{

        WEEKDAY {
            @Override
            double overtimePay(double hoursWorked, double payRate) {
                return hoursWorked>HOURS_PER_SHIFT?(hoursWorked-HOURS_PER_SHIFT)*payRate/2:0;
            }
        },

        WEEKEND {
            @Override
            double overtimePay(double hoursWorked, double payRate) {
                return hoursWorked*payRate/2;
            }
        };

        // 计算加班工资的方法,因为跟日期有关,所以定义为抽象方法,根据不同的日期去实现
        abstract double overtimePay(double hoursWorked, double payRate);

        public static final int HOURS_PER_SHIFT = 8;

        // 计算工资都是基本工资+加班工资
        double pay(double hoursWorked, double payRate) {
            double basePay=hoursWorked*payRate;
            return basePay+overtimePay(hoursWorked, payRate);
        }
    }
}

这样再需要添加新的节假日计算方式的时候,就必须要添加一个PayEnum的元素,便不会遗漏添加这种事情发生了。

三、使用枚举实现单例模式

为什么要通过枚举去实现单例模式,实现单例模式的方法有许多种,例如:饿汉模式、懒汉模式(双重锁检测)、静态内部类等方式。但是这些模式都存在一些公共的问题,例如:

  1. 如果通过反射去获取私有的构造方法来创建对象则创建的对象并非第一次创建的对象
  2. 如果类实现了Serverlization接口,反序列化之后都会创建一个新的对象

基于上面两个问题,通过枚举类型创建单例就成为了创建单例模式的最佳方法

public enum Singleton {

    INSTANCE;

    public Singleton singleton() {
        return INSTANCE;
    }
}

使用枚举方式可以完全避免反序列化而产生的创建的对象不止一个的问题,下面是《Effective Java》中的一段话 

  通过反射获取私有的构造方法,然后调用newInstance方法来创建实例在枚举类型上也是行不通的,看一下newInstance的源码

    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        ...
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ...
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

如果是枚举类型调用newInstance方法则会直接报错。 

总结

  本文介绍了枚举类型的原理,以及枚举类型的一些优点,所以在开发过程中能够使用枚举类型去替代的尽量使用枚举类型。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CAN(Controller Area Network,控制器局域网)总线协议是一种广泛应用于工业自动化、汽车电子等领域的串行通讯协议。其帧格式如下: <img src="https://img-blog.csdnimg.cn/20200925125252655.png" width="400"> CAN总线协议的帧分为标准帧和扩展帧两种,其中标准帧包含11位标识符,扩展帧包含29位标识符。在CAN总线上,所有节点都可以同时发送和接收数据,因此需要在帧中包含发送方和接收方的信息。 帧格式的具体解释如下: 1. 帧起始符(SOF):一个固定的位模式,表示帧的起始。 2. 报文控制(CTRL):包含几个控制位,如IDE、RTR等。其中IDE表示标识符的类型,0表示标准帧,1表示扩展帧;RTR表示远程请求帧,0表示数据帧,1表示远程请求帧。 3. 标识符(ID):11位或29位的标识符,用于区分不同的CAN消息。 4. 控制域(CTL):包含几个控制位,如DLC、EDL等。其中DLC表示数据长度,即数据域的字节数;EDL表示数据长度是否扩展,0表示标准数据帧,1表示扩展数据帧。 5. 数据域(DATA):0~8字节的数据。 6. CRC:用于校验数据是否正确。 7. 确认位(ACK):由接收方发送的确认信息,表示数据是否正确接收。 8. 结束符(EOF):一个固定的位模式,表示帧的结束。 以上就是CAN总线协议的帧格式。在实际应用中,节点之间通过CAN总线进行数据交换,通过解析帧中的各个字段,可以判断消息的发送方、接收方、数据内容等信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值