Java枚举的本质

目录

1.枚举简介

1.1.规范

1.2.枚举类真实的样子

1.3.枚举类的特点

1.4.枚举可以使用的方法

1.4.1.toString()方法

1.4.2.valueOf方法

1.4.3.values方法

1.4.4.ordinal方法

1.5.枚举的用法

1.5.1.常量

1.5.2.switch

1.5.3.枚举中增加方法

1.5.4.覆盖枚举方法

1.5.5.实现接口

1.5.5.1.情况 1:在 enum 类中实现接口

1.5.5.2.情况 2:让枚举类中的对象分别实现接口中的方法

1.5.6.在接口中使用枚举类

1.5.7.使用枚举集合

1.5.7.1.EnumSet

1.5.7.2.EnumMap

2.序列化和反序列化

2.1.什么是序列化和反序列化

2.2.什么是serialVersionUID序列化ID

2.3.什么类型的数据不能被序列化

3.jackson

3.1.jackson序列化writeValueAsString

3.2.jackson反序列化readValue

3.3.集合转换

3.4.@JsonProperty

3.5.ObjectMapper的一些配置

3.6.JsonParser

3.6.1.创建

3.6.2.解析

4.枚举的序列化

4.1.ordinal索引

4.2.时序图

4.3.json枚举序列化/反序列化处理

4.3.1.方法一:使用JsonCreator和JsonValue

4.3.2.方法二:自定义序列化/反序列化方法

4.4.mybatis枚举序列化/反序列化处理

4.4.1.TypeHandler

4.4.2.配置步骤

5.IDEA2019取消枚举提示

6.查看枚举调用的地方


1.枚举简介

枚举类型(enum type)是指由一组固定的常量组成合法的类型。
枚举类格式:

public enum SexEnum {
    MAN, WOMAN
}

1.1.规范

1.2.枚举类真实的样子

1、使用javac 命令编译,得到.class文件

2、使用命令 javap 对class文件进行反编译

>javac Sex.java

>javap Sex.class

得到如下结果:

Compiled from "Sex.java"

public final class Sex extends java.lang.Enum<Sex> {
  public static final Sex MAN;
  public static final Sex WOMAN;
  public static Sex[] values();
  public static Sex valueOf(java.lang.String);
  static {};
}

3、这里其实是创建了2个对象

public final class Sex extends java.lang.Enum<Sex> {
  public static final Sex MAN = new Sex();
  public static final Sex WOMAN = new Sex();
  public static Sex[] values();
  public static Sex valueOf(java.lang.String);
  static {};
}

1.3.枚举类的特点

1、枚举类是通过final修饰,不能被继承
2、枚举类默认继承了枚举类型 java.lang.Enum
3、枚举类的第一行罗列的是枚举类对象,并且是常量存储,所以枚举类的第一行写的是常量名称,默认存储了枚举对象。
4、枚举类的构造器是私有的。
5、枚举类相当于多例设计模式

1.4.枚举可以使用的方法

把上面的枚举类加上code和name

public enum SexEnum {
    MAN(1, "男"),
    WOMAN(2, "女");

    private Integer code;
    private String name;

    SexEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public Integer getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

1.4.1.toString()方法

这个方法会返回枚举常量名

// MAN
System.out.println(SexEnum.MAN.toString());
// WOMAN
System.out.println(SexEnum.WOMAN.toString());

1.4.2.valueOf方法

这个方法用于构建枚举类,传入枚举类常量名即可。

SexEnum sexEnum = SexEnum.valueOf("MAN");
// 男
System.out.println(sexEnum.getName());
try {
    // 报错: java.lang.IllegalArgumentException: No enum constant com.leelen.scd.api.amc.enums.SexEnum.UNKNOWN
    System.out.println(SexEnum.valueOf("UNKNOWN").getName());
} catch (IllegalArgumentException e) {
    e.printStackTrace();
}

如果不存在传入的枚举常量,那么会报错:
java.lang.IllegalArgumentException: 
No enum constant com.leelen.scd.api.amc.enums.SexEnum.UNKNOWN
    at java.lang.Enum.valueOf(Enum.java:238)
    at com.leelen.scd.api.amc.enums.SexEnum.valueOf(SexEnum.java:3)
    at com.leelen.scd.api.amc.enums.UseSex.main(UseSex.java:28)

1.4.3.values方法

使用枚举类名进行调用,会返回所有枚举常量的数组

1.4.4.ordinal方法

这个方法会返回枚举常量在enum中声明的位置,从0开始

SexEnum[] sexEnum = SexEnum.values();
for (SexEnum s : sexEnum) {
    System.out.println(s.getCode() + "," + s.getName() + "," + s.ordinal());
}

打印结果:
1,男,0
2,女,1

1.5.枚举的用法

1.5.1.常量

public enum ColorEnum {
    RED, GREEN, BLANK, YELLOW
}

1.5.2.switch

public static void getSexName(SexEnum sexEnum) {
    switch (sexEnum) {
        case MAN:
            System.out.println("男");
            break;
        case WOMAN:
            System.out.println("女");
            break;
        default:
            break;
    }
}

这里不要使用 SexEnum.MAN, 不然会提示:An enum switch case label must be the unqualified name of an enumeration constant

1.5.3.枚举中增加方法

public class EnumTest {
    public static void main(String[] args) {
        ErrorCodeEnum errorCode = ErrorCodeEnum.SUCCESS;
        System.out.println("状态码:" + errorCode.code() +
                " 状态信息:" + errorCode.msg());
    }
}

enum ErrorCodeEnum {
    SUCCESS(1000, "success"),
    PARAM_ERROR(1001, "parameter error"),
    SYS_ERROR(1003, "system error"),
    NAMESPACE_NOT_FOUND(2001, "namespace not found"),
    NODE_NOT_EXIST(3002, "node not exist"),
    NODE_ALREADY_EXIST(3003, "node already exist"),
    UNKNOWN_ERROR(9999, "unknown error");

    private int code;
    private String msg;

    ErrorCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int code() {
        return code;
    }

    public String msg() {
        return msg;
    }

    public static ErrorCodeEnum getErrorCode(int code) {
        for (ErrorCodeEnum it : ErrorCodeEnum.values()) {
            if (it.code() == code) {
                return it;
            }
        }
        return UNKNOWN_ERROR;
    }
}

1.5.4.覆盖枚举方法

我们可以覆盖一些枚举中的方法用于实现自己的业务,比如我们可以覆盖 toString() 方法,实现代码如下:

public class EnumTest {
    public static void main(String[] args) {
        ColorEnum colorEnum = ColorEnum.RED;
        System.out.println(colorEnum.toString());
    }
}

enum ColorEnum {
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLOW("黄色", 4);
    //  成员变量
    private String name;
    private int index;

    //  构造方法
    private ColorEnum(String name, int index) {
        this.name = name;
        this.index = index;
    }

    //覆盖方法
    @Override
    public String toString() {
        return this.index + ":" + this.name;
    }
}

1.5.5.实现接口

枚举类可以用来实现接口,但不能用于继承类,因为枚举默认继承了 java.lang.Enum 类,在 Java 语言中允许实现多接口,但不能继承多个父类,实现代码如下:

1.5.5.1.情况 1:在 enum 类中实现接口
public class EnumTest {
    public static void main(String[] args) {
        ColorEnum colorEnum = ColorEnum.RED;
        colorEnum.print();
        System.out.println("颜色:" + colorEnum.getInfo());
    }
}

interface Behaviour {
    void print();
    String getInfo();
}

enum ColorEnum implements Behaviour {
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLOW("黄色", 4);
    private String name;
    private int index;

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

    @Override
    public void print() {
        System.out.println(this.index + ":" + this.name);
    }

    @Override
    public String getInfo() {
        return this.name;
    }
}
1.5.5.2.情况 2:让枚举类中的对象分别实现接口中的方法
public enum ColorEnum implements Behaviour{
    RED("红色", 1) {
        @Override
        public void print() {

        }

        @Override
        public String getInfo() {
            return null;
        }
    }, GREEN("绿色", 2) {
        @Override
        public void print() {

        }

        @Override
        public String getInfo() {
            return null;
        }
    }, BLANK("白色", 3) {
        @Override
        public void print() {

        }

        @Override
        public String getInfo() {
            return null;
        }
    }, YELLOW("黄色", 4) {
        @Override
        public void print() {
            
        }

        @Override
        public String getInfo() {
            return null;
        }
    };
    private String name;
    private int index;

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

1.5.6.在接口中使用枚举类

我们可以在一个接口中创建多个枚举类,用它可以很好的实现“多态”,也就是说我们可以将拥有相同特性,但又有细微实现差别的枚举类聚集在一个接口中,实现代码如下:

public class EnumTest {
    public static void main(String[] args) {
        // 赋值第一个枚举类
        ColorInterface colorEnum = ColorInterface.ColorEnum.RED;
        System.out.println(colorEnum);
        // 赋值第二个枚举类
        colorEnum = ColorInterface.NewColorEnum.NEW_RED;
        System.out.println(colorEnum);
    }
}

interface ColorInterface {
    enum ColorEnum implements ColorInterface {
        GREEN, YELLOW, RED
    }
    enum NewColorEnum implements ColorInterface {
        NEW_GREEN, NEW_YELLOW, NEW_RED
    }
}

1.5.7.使用枚举集合

在 Java 语言中和枚举类相关的,还有两个枚举集合类 java.util.EnumSet 和 java.util.EnumMap,使用它们可以实现更多的功能。

1.5.7.1.EnumSet

使用 EnumSet 可以保证元素不重复,并且能获取指定范围内的元素,示例代码如下:

public class EnumTest {
    public static void main(String[] args) {
        List<ColorEnum> list = new ArrayList<>();
        list.add(ColorEnum.RED);
        list.add(ColorEnum.RED);  // 重复元素
        list.add(ColorEnum.YELLOW);
        list.add(ColorEnum.GREEN);
        // 去掉重复数据
        EnumSet<ColorEnum> enumSet = EnumSet.copyOf(list);
        System.out.println("去重:" + enumSet);

        // 获取指定范围的枚举(获取所有的失败状态)
        EnumSet<ErrorCodeEnum> errorCodeEnums = EnumSet.range(ErrorCodeEnum.ERROR, ErrorCodeEnum.UNKNOWN_ERROR);
        System.out.println("所有失败状态:" + errorCodeEnums);
    }
}

enum ColorEnum {
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLOW("黄色", 4);
    private String name;
    private int index;

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

enum ErrorCodeEnum {
    SUCCESS(1000, "success"),
    ERROR(2001, "parameter error"),
    SYS_ERROR(2002, "system error"),
    NAMESPACE_NOT_FOUND(2003, "namespace not found"),
    NODE_NOT_EXIST(3002, "node not exist"),
    NODE_ALREADY_EXIST(3003, "node already exist"),
    UNKNOWN_ERROR(9999, "unknown error");

    private int code;
    private String msg;

    ErrorCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int code() {
        return code;
    }

    public String msg() {
        return msg;
    }
}
1.5.7.2.EnumMap
public class EnumTest {
    public static void main(String[] args) {
        EnumMap<ColorEnum, String> enumMap = new EnumMap<>(ColorEnum.class);
        enumMap.put(ColorEnum.RED, "红色");
        enumMap.put(ColorEnum.GREEN, "绿色");
        enumMap.put(ColorEnum.BLANK, "白色");
        enumMap.put(ColorEnum.YELLOW, "黄色");
        System.out.println(ColorEnum.RED + ":" + enumMap.get(ColorEnum.RED));
    }
}

enum ColorEnum {
    RED, GREEN, BLANK, YELLOW;
}

2.序列化和反序列化

2.1.什么是序列化和反序列化

序列化过程:是指把一个 Java 对象变成二进制内容,实质上就是一个 byte[]。因为序列化后可以把 byte[] 保存到文件中,或者把 byte[] 通过网络传输到远程(IO),如此就相当于把 Java 对象存储到文件或者通过网络传输出去了。
一个 Java 对象要能序列化,必须实现一个特殊的java.io.Serializable接口,它的定义如下:

package java.io;
public interface Serializable {
}

Serializable 没有定义任何方法,它是一个空接口。这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

反序列化过程:把一个二进制内容(也就是 byte[])变回 Java 对象。有了反序列化,保存到文件中的 byte[] 又可以“变回” Java 对象,或者从网络上读取 byte[] 并把它“变回” Java 对象。

为什么需要序列化与反序列化?

当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。

当两个 Java 进程进行通信时,需要 Java 序列化与反序列化实现进程间的对象传送。换句话说,一方面,发送方需要把这个 Java 对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出 Java 对象。

代码:

import lombok.Data;
import java.io.Serializable;
@Data
public class Student implements Serializable {
    private String name;
    private Integer age;
    private Integer score;
}
import java.io.*;
public class TypeDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serialize();
        deserialize();
    }
    /**
     * 序列化
     */
    public static void serialize() throws IOException {
        Student student = new Student();
        student.setName("new");
        student.setAge(18);
        student.setScore(100);
        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(new FileOutputStream(new File("student.txt")));
        objectOutputStream.writeObject(student);
        objectOutputStream.close();
        System.out.println("序列化成功!已经生成student.txt文件");
        System.out.println("============================");
    }
    /**
     * 反序列化
     */
    public static void deserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream =
                new ObjectInputStream(new FileInputStream(new File("student.txt")));
        Student student = (Student) objectInputStream.readObject();
        objectInputStream.close();
        System.out.println("反序列化结果为:" + student);
    }
}

结果:

序列化成功!已经生成student.txt文件

============================

反序列化结果为:Student(name=new, age=18, score=100)

2.2.什么是serialVersionUID序列化ID

private static final long serialVersionUID = -4392658638228508589L;

serialVersionUID 是一个常数,用于唯一标识可序列化类的版本。

从输入流构造对象时,JVM 在反序列化过程中检查此常数。如果正在读取的对象的 serialVersionUID 与类中指定的序列号不同,则 JVM 抛出InvalidClassException。这是为了确保正在构造的对象与具有相同 serialVersionUID 的类兼容。

我们先调用serialize()方法把上面的Student对象序列化进student.txt文件中。然后修改Student类的内容去掉其中一个字段:

@Data
public class Student implements Serializable {
    private String name;
    private Integer age;
}

再调用反序列化方法deserialize(),结果报错:

Exception in thread "main" java.io.InvalidClassException: com.codejam.enums.demo.type.Student; local class incompatible: stream classdesc serialVersionUID = -6951954515964250676, local class serialVersionUID = 7327139321132172307

这是因为serialVersionUID 是可选的。如果不显式声明,Java 编译器将自动生成一个。

2.3.什么类型的数据不能被序列化

声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。

3.jackson

由于Spring自带的序列化和反序列化使用的是jackson,所以项目不使用fastjson。

3.1.jackson序列化writeValueAsString

先创建对象

@Data
public class Student {
    private String name;
    private Integer age;
}

创建保护集合的对象

@Data
public class Room {
    List<Student> studentList;
}

1、使用writeValueAsString将一个对象序列化为字符串

public static void main(String[] args) throws JsonProcessingException {
    ObjectMapper objectMapper=new ObjectMapper();
    Student student=new Student();
    student.setName("abc");
    student.setAge(18);

    String s = objectMapper.writeValueAsString(student);
    System.out.println(s);
}

打印:

{"name":"abc","age":18}、

2、使用writeValueAsString 将一个包含集合的对象序列化为字符串:

public static void main(String[] args) throws JsonProcessingException {
    List<Student> list = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        Student student = new Student();
        student.setName("name" + i);
        student.setAge(i);
        list.add(student);
    }
    Room classRoom = new Room();
    classRoom.setStudentList(list);

    ObjectMapper objectMapper = new ObjectMapper();
    String s = objectMapper.writeValueAsString(classRoom);
    System.out.println(s);
}

打印:

{"studentList":[{"name":"name0","age":0},{"name":"name1","age":1},{"name":"name2","age":2}]}

3.2.jackson反序列化readValue

使用readValue将字符串转换为student对象

public static void main(String[] args) throws JsonProcessingException {
    ObjectMapper objectMapper=new ObjectMapper();
    String s = "{\"name\":\"abc\",\"age\":18}";
    Student student = objectMapper.readValue(s, Student.class);
    System.out.println(student);
}

使用readValue将字符串转为对象(包含集合)

public static void main(String[] args) throws JsonProcessingException {
    String s = "{\"studentList\":[{\"name\":\"name0\",\"age\":0},{\"name\":\"name1\",\"age\":1},{\"name\":\"name2\",\"age\":2}]}";
    ObjectMapper objectMapper = new ObjectMapper();
    Room room = objectMapper.readValue(s, Room.class);
    System.out.println(room);
}

3.3.集合转换

public static void main(String[] args) throws JsonProcessingException {
    String listStr = "[{\"name\":\"李四\",\"age\":1},{\"name\":\"张三\",\"age\":2}]";
    ObjectMapper objectMapper = new ObjectMapper();
    List<Student> students = objectMapper.readValue(listStr, new TypeReference<List<Student>>() {});
    System.out.println(students);
}

3.4.@JsonProperty

如果序列化的时候,名称要更改的话,则可以使用@JsonProperty

3.5.ObjectMapper的一些配置

通过以下设置会在序列化和反序列化时忽略无法解析和为空的字段

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

3.6.JsonParser

JsonParser 类是底层 Json解析器。
JsonParser实现相较于 ObjectMapper 更底层,因此解析速度更快,但相对复杂。

3.6.1.创建

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;

public static void main(String[] args) throws IOException {
    String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

    JsonFactory factory = new JsonFactory();
    JsonParser parser = factory.createParser(carJson);
}

createParser()方法传入 ReaderInputStreamURLbyte[] 或 char[] 参数可以实现解析不同来源 json 数据。

3.6.2.解析

JsonParser 工作方式是将 json 分解成一系列标记 (token) ,逐个迭代这些标记进行解析

public static void main(String[] args) throws IOException {
    String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

    JsonFactory factory = new JsonFactory();
    JsonParser parser  = factory.createParser(carJson);

    while(!parser.isClosed()){
        JsonToken jsonToken = parser.nextToken();
        System.out.println("jsonToken = " + jsonToken);
    }
}

输出结果:

jsonToken = START_OBJECT

jsonToken = FIELD_NAME

jsonToken = VALUE_STRING

jsonToken = FIELD_NAME

jsonToken = VALUE_NUMBER_INT

jsonToken = END_OBJECT

jsonToken = null

通过 JsonParser 的 nextToken() 方法获得 JsonToken,我们可以检查 JsonToken 实例的类型,JsonToken 类提供了一组常量表示标记类型:

package com.fasterxml.jackson.core;
public enum JsonToken
{
   NOT_AVAILABLE(null, JsonTokenId.ID_NOT_AVAILABLE),
   START_OBJECT("{", JsonTokenId.ID_START_OBJECT),
   END_OBJECT("}", JsonTokenId.ID_END_OBJECT),
   START_ARRAY("[", JsonTokenId.ID_START_ARRAY),
   END_ARRAY("]", JsonTokenId.ID_END_ARRAY),
   FIELD_NAME(null, JsonTokenId.ID_FIELD_NAME),
   VALUE_EMBEDDED_OBJECT(null, JsonTokenId.ID_EMBEDDED_OBJECT),
   VALUE_STRING(null, JsonTokenId.ID_STRING),
   VALUE_NUMBER_INT(null, JsonTokenId.ID_NUMBER_INT),
   VALUE_NUMBER_FLOAT(null, JsonTokenId.ID_NUMBER_FLOAT),
   VALUE_TRUE("true", JsonTokenId.ID_TRUE),
   VALUE_FALSE("false", JsonTokenId.ID_FALSE),
   VALUE_NULL("null", JsonTokenId.ID_NULL),
        ;
}

如果标记指针指向的是字段,JsonParser 的 getCurrentName() 方法返回当前字段名称。

getValueAsString() 返回当前标记值的字符串类型,同理 getValueAsInt() 返回整型值
其他方法

public static void main(String[] args) throws IOException {
    String json = "{ \"name\" : \"tom\", \"age\" : 28, \"height\": 1.75, \"ok\": true}";

    JsonFactory factory = new JsonFactory();
    JsonParser parser = factory.createParser(json);

    while (!parser.isClosed()) {
        JsonToken token = parser.nextToken();
        if (JsonToken.FIELD_NAME == token) {
            String fieldName = parser.getCurrentName();
            System.out.print(fieldName + ": ");

            parser.nextToken();
            switch (fieldName) {
                case "name":
                    System.out.println(parser.getValueAsString());
                    break;
                case "age":
                    System.out.println(parser.getValueAsInt());
                    break;
                case "height":
                    System.out.println(parser.getValueAsDouble());
                    break;
                case "ok":
                    System.out.println(parser.getValueAsBoolean());
                    break;
            }
        }
    }
}

4.枚举的序列化

4.1.ordinal索引

枚举默认是使用索引ordinal 来进行序列化反序列化操作的,
例如我这边定义了枚举:1, 3  它对应的索引是0,1

@Getter
@Accessors(fluent = true)
@AllArgsConstructor
public enum AmcManagementModeEnum implements IDictionaryEnum {
    UNIFIED_AUTHORIZATION_MANAGEMENT(1, "统一授权管理"),
    //SEMI_AUTHORIZED_MANAGEMENT(2, "半授权管理"),
    INDEPENDENT_MANAGEMENT(3, "独立管理"),

    ;

    /**
     * 字典码值
     */
    private Integer code;
    /**
     * 字典描述
     */
    private String desc;
}

前端请求0的时候,对应的是第一个 1, "统一授权管理"。
请求1的时候,对应的是第二个 3, "独立管理"。

但如果我请求2的话,则会报错越界了。

Invalid JSON input: Cannot deserialize value of type `com.leelen.scd.module.amc.enums.AmcManagementModeEnum` from number 2: index value outside legal index range [0..1]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.leelen.scd.module.amc.enums.AmcManagementModeEnum` from number 2: index value outside legal index range [0..1]\n at [Source: (PushbackInputStream); line: 17, column: 31] (through reference chain: com.leelen.scd.base.common.entity.RequestDTO[\"body\"]->com.leelen.scd.base.common.entity.PagerReqDTO[\"params\"]->com.leelen.scd.module.amc.vo.AmcNeighInfoPageReq[\"managementMode\"])

4.2.时序图

4.3.json枚举序列化/反序列化处理

4.3.1.方法一:使用JsonCreator和JsonValue

JsonCreator :标记在反序列化时的初始化函数,入参为对应该枚举类型的值。

JsonVale:标记在序列化时枚举对应生成的值。

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.leelen.scd.base.common.enums.IDictEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;

import java.util.Objects;

@Getter
@Accessors(fluent = true)
@AllArgsConstructor
public enum AmcManagementModeEnum implements IDictEnum{
    UNIFIED_AUTHORIZATION_MANAGEMENT( 1, "统一授权管理"),
    //SEMI_AUTHORIZED_MANAGEMENT(2, "半授权管理"),
    INDEPENDENT_MANAGEMENT(3, "独立管理"),

    ;

    /**
     * 字典码值
     */
    private Integer code;
    /**
     * 字典描述
     */
    private String desc;

    /**
     * 处理入参,定义转换函数parse,将code值入参转成对应的枚举类型
     * 在反序列化的时候Jackson会自动调用这个方法去自动帮我们转换
      */
    @JsonCreator
    public static AmcManagementModeEnum parse(Integer code) {
        if (Objects.isNull(code)) {
            return null;
        }
        for (AmcManagementModeEnum item : AmcManagementModeEnum.values()) {
            if (item.code.equals(code)) {
                return item;
            }
        }
        return null;
    }
    /**
     * 处理出参,在getter方法标记序列化后的值
      */
    @JsonValue
    public Integer code() {
        return code;
    }

}

使用枚举:
请求参数:

import com.leelen.scd.module.amc.enums.AmcManagementModeEnum;
import lombok.Data;
@Data
public class AmcNeighInfoPageReq {
    /**
     * 小区管理模式
     */
    private AmcManagementModeEnum managementMode;
}

响应参数:

@Data
public class AmcNeighInfoPageRes {
    /**
     * 小区管理模式
     */
    private AmcManagementModeEnum managementMode;
}

验证:

@RestController
@RequestMapping("/web/system/community/amc")
public class AmcNeighInfoController {

    /**
     * 分页
     */
    @RequestMapping("/page")
    public ResponseDTO<AmcNeighInfoPageRes> page(@RequestBody final RequestDTO<PagerReqDTO<AmcNeighInfoPageReq>> request) {
        AmcNeighInfoPageRes res = new AmcNeighInfoPageRes();
        res.setManagementMode(request.getBody().getParams().getManagementMode());
        return ResponseHelper.successResponse(request.getHeader(), res);
    }
}

4.3.2.方法二:自定义序列化/反序列化方法

1、首先定义接口IDictEnum。 
这里指定反序列化的方法:EnumJsonDeserializer
这里指定序列化的方法:EnumJsonSerializer

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@JsonDeserialize(using = EnumJsonDeSerializer.class)
@JsonSerialize(using = EnumJsonSerializer.class)
public interface IDictEnum {
    /**
     * 获得字典码值
     */
    Integer code();

    /**
     * 获得字典描述, 这里不能用name, 因为java.lang.Enum已经定义了name
     */
    String desc();

}

2、反序列化方法:EnumJsonDeserializer

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;

import java.io.IOException;
import java.util.Arrays;

public class EnumJsonDeSerializer extends JsonDeserializer<IDictEnum> implements ContextualDeserializer {

    private Class<? extends IDictEnum> clazz;

    public EnumJsonDeSerializer() {
    }

    public EnumJsonDeSerializer(Class<? extends IDictEnum> clazz) {
        this.clazz = clazz;
    }

    @Override
    public IDictEnum deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
        String param = jsonParser.getText();
        IDictEnum[] enumConstants = clazz.getEnumConstants();
        JsonStreamContext parsingContext = jsonParser.getParsingContext();
        IDictEnum iDictEnum = Arrays.stream(enumConstants)
                .filter(x -> {
                    //x.toString(),取枚举的具体值,如:xxx.enums.share.DelFlagEnum 枚举里的“NOT_DELETE”
                    //从而使得两种形式都能识别
                    String enumCodeStr = x.toString();
                    return enumCodeStr.equals(param) || param.equals(x.code() + "");
                })
                .findFirst()
                .orElse(null);
        /*if (null == iEnum) {
            String msg = String.format("枚举类型%s从%s未能转换成功", clazz.toString(), param);
            throw new Exception(msg);
        }*/
        return iDictEnum;
    }

    @Override
    public Class<?> handledType() {
        return IDictEnum.class;
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JsonMappingException {
        JavaType type = property.getType();
        // 如果是容器,则返回容器内部枚举类型
        while (type.isContainerType()) {
            type = type.getContentType();
        }
        return new EnumJsonDeSerializer((Class<? extends IDictEnum>) type.getRawClass());
    }
}

3、序列化方法:EnumJsonSerializer

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class EnumJsonSerializer extends JsonSerializer<IDictEnum> {
    public void serialize(IDictEnum iDictEnum, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
        // 序列化只要code的值
        generator.writeNumber(iDictEnum.code());
        // 序列化形式: {"code": "", "desc": ""}
        //generator.writeStartObject();
        //generator.writeNumberField("code", iBaseDict.code());
        //generator.writeStringField("desc", iBaseDict.desc());
        //generator.writeEndObject();
    }
}

经验证以下这几种情况都能很好的识别成功!!!

@Data
public class AmcNeighInfoPageReq {
    private AmcManagementModeDictEnum managementMode;
    private List<AmcManagementModeDictEnum> managementModeList;
    private AmcNeighInfoPageReq amcNeighInfoPageReq;
    private Map<Integer, AmcManagementModeDictEnum> map1;
    private Map<AmcManagementModeDictEnum, Integer> map2;
}

4.4.mybatis枚举序列化/反序列化处理

以上两种方法对于mybatis来说没有起到作用,需要单独进行mybatis自定义序列化

MyBatis内置了两个枚举转换器分别是:org.apache.ibatis.type.EnumTypeHandler和org.apache.ibatis.type.EnumOrdinalTypeHandler。

  • EnumTypeHandler是默认的枚举转换器,该转换器将枚举实例转换为实例名称的字符串。比如有个枚举。

例如 前端输入1的话,则后端insert的时候的值是

字符串“UNIFIED_AUTHORIZATION_MANAGEMENT”

@Getter
@Accessors(fluent = true)
@AllArgsConstructor
public enum AmcManagementModeEnum implements IDictEnum {
    UNIFIED_AUTHORIZATION_MANAGEMENT( 1, "统一授权管理"),
    //SEMI_AUTHORIZED_MANAGEMENT(2, "半授权管理"),
    INDEPENDENT_MANAGEMENT(3, "独立管理"),

    ;

    /**
     * 字典码值
     */
    private Integer code;
    /**
     * 字典描述
     */
    private String desc;
}
  • EnumOrdinalTypeHandler这个转换器将枚举实例的ordinal属性作为取值(从0依次取值)。还是上面的例子用这种转换器保存在数据库中的值就是0。

如果我们想保存枚举本身所定义的code值呢?这就需要自定义一个类型转换器,自定义一个int类型保存在数据库,即insert时枚举转换为int型数据保存在数据库,select时数据库中的int值转换成实体类的枚举类型。

4.4.1.TypeHandler

TypeHandler,顾名思义类型转换器,就是将数据库中的类型与Java中的类型进行相互转换的处理器。

经常自定义类型转换器方式有两种,实现 TypeHandler 接口, 或继承抽象类 BaseTypeHandle,并且可以指定转换后的字段类型。

  • 其实BaseTypeHandler也是继承了TypeHandler接口,在实现的TypeHandler接口的方法中调用的是自身抽象方法

抽象类BaseTypeHandler的抽象方法

// 执行之前,将Java类型转换为对应的jdbc类型,用于赋值sql中参数
public abstract void setNonNullParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;
// 根据列名从resultSet中获取,将JDBC类型转换为Java类型
public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;
// 根据下标从resultSet中获取,将JDBC类型转换为Java类型
public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;
// 用于在执行完存储过程后,将JDBC类型转换为Java类型
public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;

4.4.2.配置步骤

1、mybatis自定义枚举类型转换 (这里需要配置@MappedTypes)

import com.leelen.scd.base.common.util.EnumUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@MappedTypes({IDictEnum.class})
public class EnumTypeHandler<E extends Enum<?> & IDictEnum> extends BaseTypeHandler<IDictEnum> {
    private Class<E> type;

    public EnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null.");
        }
        this.type = type;
    }

    /**
     * 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, IDictEnum parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setInt(i, parameter.code());
    }

    /**
     * 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型
     */
    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return rs.wasNull() ? null : codeOf(code);
    }

    /**
     * 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型
     */
    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : codeOf(code);
    }

    /**
     * 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型
     */
    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : codeOf(code);
    }

    private E codeOf(int code) {
        try {
            return EnumUtil.getEnumByCode(type, code);
        } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + code + " to " + type.getSimpleName() + " by code value.", ex);
        }
    }
}

这里用到了工具类:

public class EnumUtil {

    /**
     * 根据code获取枚举
     */
    public static <T extends IDictEnum> T getEnumByCode(Class<T> tClass, Integer code) {
        if (code != null) {
            for (T t : tClass.getEnumConstants()) {
                if (t.code().equals(code)) {
                    return t;
                }
            }
        }
        return null;
    }
}

2、定义mybatis的typeHandler扫描包路径:

# mybatis配置参数
mybatis:
  # 定义typeHandler扫描包路径
  type-handlers-package: com.leelen.scd

5.IDEA2019取消枚举提示

IDEA2019枚举自带入参提示,个人感觉看的比较眼花,建议把他取消掉。

Settings - Editor - Inlay Hints - Java 选择Parameter hints取消勾选Enum constants

效果如下:

6.查看枚举调用的地方

我们通常使用 ctrl + 鼠标左键,来查看方法被哪些地方调用,但是枚举却没法这么使用。

解决方法,使用alt + F7 来间接查看调用的地方,或者右键,选择Find Usage

或者使用快捷键 ctl + alt + F7

也可以在idea配置提示:

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值