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配置提示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/641477.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

热题系列章节1

22. 括号生成 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”] 示例 2&#xff1a…

LeetCode/NowCoder-链表经典算法OJ练习3

孜孜不倦&#xff1a;孜孜&#xff1a;勤勉&#xff0c;不懈怠。指工作或学习勤奋不知疲倦。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;返回倒数第k个节点 题目二&#xff1a;链表的回文结构 题目三&#xff1a;相交链表 SUMUP结尾 说在前…

两篇文章讲透数据结构之堆(一)!

目录 1.堆的概念 2.堆的实现方式 3.堆的功能 4.堆的声明 5.堆的实现 5.1堆的初始化 5.2堆的插入 5.2.1向上调整算法 5.2.2堆的插入 5.3堆的删除 5.3.1向下调整算法 5.3.2堆的删除 5.4获取堆顶元素 5.5获取堆的元素个数 5.6判断堆是否为空 5.7打印堆 5.8建堆 …

SQL开窗函数

文章目录 概念&#xff1a;语法&#xff1a;常用的窗口函数及示例&#xff1a;求平均值&#xff1a;AVG() &#xff1a;求和&#xff1a;SUM():求排名&#xff1a;移动平均计数COUNT():求最大MXA()/小MIN()值求分区内的最大/最小值求当前行的前/后一个值 概念&#xff1a; 开窗…

算法题1:电路开关(HW)

题目描述 实验室对一个设备进行通断测试,实验员可以操控开关进行通断,有两种情况: ps,图没记下来,凭印象画了类似的 初始时,3个开关的状态均为断开;现给定实验员操控记录的数组 records ,records[i] = [time, switchId],表示在时刻 time 更改了开关 switchId 的状态…

多线程(C++11)

多线程&#xff08;C&#xff09; 文章目录 多线程&#xff08;C&#xff09;前言一、std::thread类1.线程的创建1.1构造函数1.2代码演示 2.公共成员函数2.1 get_id()2.2 join()2.3 detach()2.4 joinable()2.5 operator 3.静态函数4.类的成员函数作为子线程的任务函数 二、call…

AOP编程

AOP编程 AOP&#xff0c;面向切面编程&#xff0c;一种编程范式&#xff0c;指导开发者如何组织程序结构。 OOP&#xff0c;面向对象编程&#xff0c;一种编程思想。 AOP&#xff0c;提供了一种机制,可以将一些横切系统中多个模块的共同逻辑(如日志记录、事务管理、安全控制等…

SQL面试题练习 —— 波峰波谷

来源&#xff1a;字节今日头条 目录 1 题目2 建表语句3 题解 1 题目 有如下数据&#xff0c;记录每天每只股票的收盘价格&#xff0c;请查出每只股票的波峰和波谷的日期和价格&#xff1b; 波峰定义&#xff1a;股票价格高于前一天和后一天价格时为波峰 波谷定义&#xff1a;股…

MoE 系列论文解读:Gshard、FastMoE、Tutel、MegaBlocks 等

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接…

Unity在Windows平台播放HEVC/H.265格式视频的底层原理

相关术语、概念 HEVC/H.265 HEVC&#xff08;High Efficiency Video Coding&#xff09;是一种视频压缩标准&#xff0c;也被称为H.265。它是一种高效的视频编码标准&#xff0c;可以提供比之前的标准&#xff08;如H.264&#xff09;更高的压缩率&#xff0c;同时保持较高的…

力扣HOT100 - 31. 下一个排列

解题思路&#xff1a; 数字是逐步增大的 步骤如下&#xff1a; class Solution {public void nextPermutation(int[] nums) {int i nums.length - 2;while (i > 0 && nums[i] > nums[i 1]) i--;if (i > 0) {int j nums.length - 1;while (j > 0 &&…

015_表驱动编程思想(c实现)

【背景】 数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条&#xff0c;正确的算法就不言自明。编程的核心是数据结构&#xff0c;而不是算法。 ——Rob Pike 上面是这个名人说过的话&#xff0c;那么c语言之父 丹尼斯麦卡利斯泰尔里奇 的《c程序设计》里曾经…

【Linux取经路】基于信号量和环形队列的生产消费者模型

文章目录 一、POSIX 信号量二、POSIX 信号量的接口2.1 sem_init——初始化信号量2.2 sem_destroy——销毁信号量2.3 sem_wait——等待信号量2.4 sem_post——发布信号量 三、基于环形队列的生产消费者模型3.1 单生产单消费模型3.2 多生产多消费模型3.3 基于任务的多生产多消费模…

C# 利用Xejen框架源码,我们来开发一个基于Dapper技术的数据库通用的帮助访问类,通过Dapper的增删改查,可以访问Sqlite数据库

Dapper 是一个轻量级的对象关系映射&#xff08;ORM&#xff09;工具&#xff0c;适用于 .NET 平台。它由 Stack Overflow 团队开发&#xff0c;旨在提供简单、高效的数据访问功能。与其他重量级 ORM&#xff08;如 Entity Framework&#xff09;相比&#xff0c;Dapper 更加轻…

用这8种方法在海外媒体推广发稿平台上获得突破-华媒舍

在今天的数字时代&#xff0c;海外媒体推广发稿平台已经成为了许多机构和个人宣传和推广的有效途径。如何在这些平台上获得突破并吸引更多的关注是一个关键问题。本文将介绍8种方法&#xff0c;帮助您在海外媒体推广发稿平台上实现突破。 1. 确定目标受众 在开始使用海外媒体推…

C++语法|虚函数与多态详细讲解(六)|如何解释多态?(面试向)

系列汇总讲解&#xff0c;请移步&#xff1a; C语法&#xff5c;虚函数与多态详细讲解系列&#xff08;包含多重继承内容&#xff09; 多态分为了两种&#xff0c;一种是静态的多态&#xff0c;一种是动态的多态。 静态&#xff08;编译时期&#xff09;的多态 函数重载 boo…

pands使用openpyxl引擎实现EXCEL条件格式

通过python的openpyxl库&#xff0c;实现公式条件格式。 实现内容&#xff1a;D列单元格不等于E列同行单元格时标红。 #重点是formula后面的公式不需要“”号。 from openpyxl.styles import Color, PatternFill, Font, Border from openpyxl.styles.differential import Dif…

【设计模式】JAVA Design Patterns——Bytecode(字节码模式)

&#x1f50d;目的 允许编码行为作为虚拟机的指令 &#x1f50d;解释 真实世界例子 一个团队正在开发一款新的巫师对战游戏。巫师的行为需要经过精心的调整和上百次的游玩测试。每次当游戏设计师想改变巫师行为时都让程序员去修改代码这是不妥的&#xff0c;所以巫师行为以数据…

git push后一直卡在在Writing objects:问题

git push后一直卡在Writing objects: 解决&#xff1a;设置 git config --global http.postBuffer 5242880000在执行git push。 一般设置后就可以成功了&#xff0c;后面不用看。 2. 我这里结果又报错&#xff1a; fatal: protocol error: bad line length 8192 MiB | 107.46 …

【C++】d1

关键字&#xff1a; 运行、前缀、输入输出、换行 运行f10 前缀必须项&#xff1a; #include <iostream> using namespace std; 输入/输出&#xff1a; cin >> 输入 cout << 输出 语句通过>>或<<分开 换行 endl或者"\n"