再探枚举类Enum

9 篇文章 1 订阅
本文介绍了Java枚举的定义、使用场景和高级用法,如简化时间解析,同时讨论了枚举在接口返回值中可能引发的版本兼容问题及其解决策略。枚举提供类型检查、含义明确等优点,但在服务端更新枚举类型时,可能导致客户端反序列化异常。解决方案包括避免在接口返回值中使用枚举或配置反序列化处理未知枚举值。
摘要由CSDN通过智能技术生成

枚举类是 Java 很常见的类了,最近在推上看到一个很有意思的枚举用法,加上执行开发中碰到的枚举的一些问题,这里记录一下。

枚举定义和使用场景

枚举是一个被命名的整形常数的集合。在 Java 中,描述抽象的事物外面用类和接口,但有些特殊的类的属性是一组固定的常数集合,那么就可以用枚举来表示,典型的例子比如星期有周日到周一,人类的性别有男女…嗯,如果见过国外的一些网站可能会有一些跨性别或其他选项可选,不过还是一样是常数集合。

比如一个 Person 类,性别 gender 用0表示女的,1表示男的,在数据库字段也是存的0或1的tinyint类型。

public class Person {
    //名字
    String name;
    //性别:0女,1男
    int gender;
    /**
     * getter/setter 省略
     * ...
     */
}

这种方式客户端拿到 Person 对象之后看到 gender 的值后,并不知道0或1代表什么,只能看注释或者文档。

使用 Enum 之后的写法:

public class Person {
    //名字
    String name;
    //性别
    Gender gender;
    //getter/setter/toString 省略
}

//性别枚举
public enum Gender {

    Female(0),
    Male(1);

    private int gender;
    Gender(int gender) {
        this.gender = gender;
    }

    public int getVal() {
        return gender;
    }
}

//使用
public static void main(String[] args) {
    Person p = new Person();
    p.setName("nathan");
    //只能传入Gender存在的类型
    p.setGender(Gender.Male);

    System.out.println(p);
    //可以获知属性值代表什么类型
    System.out.println("gender:" + p.getGender() + "/value:" + p.getGender().getVal());
}

从上面可以看出,枚举类有三个好处。

  • 枚举也是类,可以有属性和方法
  • 类型检查和有效性检查
  • 本身就带有含义,不用像常量那样需要查询文档或注释才知道值定义

高级用法

之前在推上别人看到用 Enum 来简化时间解析,可以拥有类型和有效性检查的好处。用法是这样的;

public enum DateUtil {

    yyyy_MM_dd("yyyy-MM-dd"),
    MMM_dd_yyyy("MMM dd, yyyy");

    private final DateTimeFormatter formatter;

    DateUtil(final String formatString) {
        formatter = DateTimeFormatter.ofPattern(formatString);
    }

    public LocalDate parse(final String string) {
        return LocalDate.parse(string, formatter);
    }

    public String format(final LocalDate date) {
        return formatter.format(date);
    }

    public Date parseDate(final String string) {
        return Date.from(parse(string).atStartOfDay()
                .atZone(ZoneId.systemDefault())
                .toInstant());
    }

    public String format(final Date date) {
        return format(date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDate());
    }

    public static void main(String[] args) {
        //按照枚举定义的格式转化成LocalDate
        LocalDate parse = DateUtil.yyyy_MM_dd.parse("2022-03-31");
        System.out.println(parse);

        //把LocalDate转换成字符串
        String format = DateUtil.MMM_dd_yyyy.format(LocalDate.now());
        System.out.println(format);
    }
}

枚举的问题

看过《阿里巴巴Java开发手册》应该注意到其中对枚举类的使用做了限制,二方库里可定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POLO 对象。原因是使用枚举作为返回值,若 RPC 客户端和服务端版本不一致的话,会造成反序列化异常。

以上面 Person 类为例,版本1的 Gender 属性有两个类型 Male 和 Female,将这个版本的 SDK 给客户端用。后来需求变化,版本2的 Gender 加了 Transgender,如:

public enum Gender {

    Female(0),
    Male(1),
    Transgender(2);

    private int gender;
    Gender(int gender) {
        this.gender = gender;
    }

    public int getVal() {
        return gender;
    }
}

如果客户端还是使用旧的 SDK 的进行请求调用的话,涉及序列化就会出现错误。

以 Jackson 的序列化反序列化为例,Gender 新加类型序列化:

//版本2
Person p = new Person();
p.setName("nathan");
p.setGender(Gender.Transgender);
String ps = new ObjectMapper().writeValueAsString(p);
//{"name":"nathan","gender":"Transgender"}

客户端反序列化:

String pStr = "{\"name\":\"nathan\",\"gender\":\"Transgender\"}";
Person person = mapper.readValue(pStr, Person.class);

报错

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of com.review.enumtest.Gender from String value 'Transgender': value not one of declared Enum instance names: [Female, Male]
 at [Source: {"name":"nathan","gender":"Transgender"}; line: 1, column: 17] (through reference chain: com.review.enumtest.Person["gender"])

解决方法就是接口返回值不要用枚举,或者客户端需要自行处理不存在枚举类型,比如转换成 null 或者设置为默认值。

String pStr = "{\"name\":\"nathan\",\"gender\":\"Transgender\"}";
Person person = mapper
                .configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
                .readValue(pStr, Person.class);

两种处理方式的分歧是在认为枚举应不应该允许变化的,使用哪种看自己业务的需求。可以看看知乎上关于 Enum 反序列化问题的讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值