目录
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()方法传入 Reader, InputStream, URL, byte[] 或 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配置提示: