MapStruct教程-枚举的五种用法

开发 前端
日常开发中,我们经常会用到枚举,有时候会涉及枚举之间的映射、枚举与int或String之间的映射等。本文一起看下,MapStruct中如何实现。

你好,我是看山。

日常开发中,我们经常会用到枚举,有时候会涉及枚举之间的映射、枚举与int或String之间的映射等。本文一起看下,MapStruct中如何实现。

一、将一个枚举映射到另一个枚举

(一)用例说明

  • 在 REST API 中,将外部API状态码转换为我们应用内部的状态枚举;
  • 与第三方库集成时,两个服务间枚举定义不同,通常需要处理枚举映射。

(二)使用MapStruct实现映射

这里我们会用到@ValueMapping注解,可以实现源常量值到目标常量值的映射。

我们看下实际应用。首先定义一个表示交通信号的枚举TrafficSignal:

public enum TrafficSignal {
    OFF,
    STOP,
    GO;
}

在定义一个表示道路标记的源枚举RoadSign:

public enum RoadSign {
    OFF,
    HALT,
    MOVE;
}

接下来,我们定义一个映射:

@Mapper
public interface TrafficSignalMapper {
    TrafficSignalMapper INSTANCE = Mappers.getMapper(TrafficSignalMapper.class);

    @ValueMapping(target = "GO", source = "MOVE")
    @ValueMapping(target = "STOP", source = "HALT")
    TrafficSignal toTrafficSignal(RoadSign source);
}

看下生成的实现:

public class TrafficSignalMapperImpl implements TrafficSignalMapper {

    @Override
    public TrafficSignal toTrafficSignal(RoadSign source) {
        if ( source == null ) {
            return null;
        }

        TrafficSignal trafficSignal;

        switch ( source ) {
            case MOVE: trafficSignal = TrafficSignal.GO;
            break;
            case HALT: trafficSignal = TrafficSignal.STOP;
            break;
            case OFF: trafficSignal = TrafficSignal.OFF;
            break;
            default: throw new IllegalArgumentException( "Unexpected enum constant: " + source );
        }

        return trafficSignal;
    }
}

可以看到,因为OFF是名字相同,虽然没有定义映射关系,MapStruct会自动匹配。剩下两个枚举值根据我们的定义匹配上了。

这里需要注意的是,我们需要确保将源枚举的所有值都映射到目标枚举,如果没有完全匹配上,会走到default分支,抛出IllegalArgumentException异常。

二、将字符串映射到枚举

我们继续看下字符串与枚举之间的映射。有了前面的基础,我们这里直接上手,还是使用@ValueMapping注解,字符串的值都是小写,需要转换为TrafficSignal枚举:

@ValueMapping(target = "OFF", source = "off")
@ValueMapping(target = "GO", source = "move")
@ValueMapping(target = "STOP", source = "halt")
TrafficSignal stringToTrafficSignal(String source);

我们看下生成的代码:

@Override
public TrafficSignal stringToTrafficSignal(String source) {
    if ( source == null ) {
        return null;
    }

    TrafficSignal trafficSignal;

    switch ( source ) {
        case "off": trafficSignal = TrafficSignal.OFF;
        break;
        case "move": trafficSignal = TrafficSignal.GO;
        break;
        case "halt": trafficSignal = TrafficSignal.STOP;
        break;
        case "OFF": trafficSignal = TrafficSignal.OFF;
        break;
        case "STOP": trafficSignal = TrafficSignal.STOP;
        break;
        case "GO": trafficSignal = TrafficSignal.GO;
        break;
        default: throw new IllegalArgumentException( "Unexpected enum constant: " + source );
    }

    return trafficSignal;
}

可以看到,除了我们定义的三个映射,MapStruct还会自动将枚举的name()也作为映射依据,换句话说,如果我们输入的字符串与枚举正好是一一对应的,那就可以不用定义映射关系了。

三、处理自定义名称转换

还有一种情况是,需要映射的枚举值有统一的约束,比如遵循不同的大小写、前缀或后缀等,比如,一个信号可以是Go、go、GO、Go_Value、Value_Go等。

(一)后缀

假如我们的目标枚举相较于源枚举有统一的后缀,比如:GO到GO_VALUE。

public enum TrafficSignalSuffixed {
    OFF_VALUE,
    STOP_VALUE,
    GO_VALUE
}

此时,我们可以用到@EnumMapping注解,定义名称转换策略是后缀,然后定义后缀值:

@EnumMapping(nameTransformationStrategy = SUFFIX_TRANSFORMATION, configuration = "_VALUE")
TrafficSignalSuffixed applySuffix(TrafficSignal source);

@EnumMapping为枚举类型定义自定义映射,nameTransformationStrategy指定在映射之前应用于枚举常量名称的转换策略,并使用configuration定义控制值。

生成结果是:

@Override
public TrafficSignalSuffixed applySuffix(TrafficSignal source) {
    if ( source == null ) {
        return null;
    }

    TrafficSignalSuffixed trafficSignalSuffixed;

    switch ( source ) {
        case OFF: trafficSignalSuffixed = TrafficSignalSuffixed.OFF_VALUE;
        break;
        case STOP: trafficSignalSuffixed = TrafficSignalSuffixed.STOP_VALUE;
        break;
        case GO: trafficSignalSuffixed = TrafficSignalSuffixed.GO_VALUE;
        break;
        default: throw new IllegalArgumentException( "Unexpected enum constant: " + source );
    }

    return trafficSignalSuffixed;
}

需要注意,@EnumMapping应用的场景是枚举值完全符合指定策略,如果其中有某个值不符合,编译时会出现异常“The following constants from the source enum have no corresponding constant in the target enum and must be be mapped via adding additional mappings: xxx.”

(二)前缀

假如我们的目标枚举相较于源枚举有统一的前缀缀,比如:GO到VALUE_GO。

public enum TrafficSignalPrefixed {
    VALUE_OFF,
    VALUE_STOP,
    VALUE_GO;
}

定义映射:

@EnumMapping(nameTransformationStrategy = PREFIX_TRANSFORMATION, configuration = "VALUE_")
TrafficSignalPrefixed applyPrefix(TrafficSignal source);

PREFIX_TRANSFORMATION是告诉MapStruct,需要在源枚举增加前缀VALUE_。

生成的代码是:

public TrafficSignalPrefixed applyPrefix(TrafficSignal source) {
    if ( source == null ) {
        return null;
    }

    TrafficSignalPrefixed trafficSignalPrefixed;

    switch ( source ) {
        case OFF: trafficSignalPrefixed = TrafficSignalPrefixed.VALUE_OFF;
        break;
        case STOP: trafficSignalPrefixed = TrafficSignalPrefixed.VALUE_STOP;
        break;
        case GO: trafficSignalPrefixed = TrafficSignalPrefixed.VALUE_GO;
        break;
        default: throw new IllegalArgumentException( "Unexpected enum constant: " + source );
    }

    return trafficSignalPrefixed;
}

(三)去除后缀

假如我们的目标枚举相较于源枚举缺少统一的后缀,比如:GO_VALUE到GO。

我们可以使用STRIP_SUFFIX_TRANSFORMATION指定去除后缀:

@EnumMapping(nameTransformationStrategy = STRIP_SUFFIX_TRANSFORMATION, configuration = "_VALUE")
TrafficSignal stripSuffix(TrafficSignalSuffixed source);

(四)去除前缀

假如我们的目标枚举相较于源枚举缺少统一的前缀,比如:VALUE_GO到GO。

我们可以使用STRIP_PREFIX_TRANSFORMATION指定去除前缀:

@EnumMapping(nameTransformationStrategy = STRIP_PREFIX_TRANSFORMATION, configuration = "VALUE_")
TrafficSignal stripPrefix(TrafficSignalPrefixed source);

(五)小写

假如我们的目标枚举是源枚举的小写,比如:GO变为go:

public enum TrafficSignalLowercase {
    off,
    stop,
    go;
}

我们需要使用CASE_TRANSFORMATION策略,并定义策略是lower。

定义映射:

@EnumMapping(nameTransformationStrategy = CASE_TRANSFORMATION, configuration = "lower")
TrafficSignalLowercase applyLowercase(TrafficSignal source);

(六)大写

假如我们的目标枚举是源枚举的小写,比如:go变为GO:

还是使用CASE_TRANSFORMATION策略,并定义策略是upper。

@EnumMapping(nameTransformationStrategy = CASE_TRANSFORMATION, configuration = "upper")
TrafficSignal applyUppercase(TrafficSignalLowercase source);

(七)首字母大写

我们还可以指定首字母大写的映射,例如,go变为Go。

定义下目标枚举

public enum TrafficSignalCapital {
    Off,
    Stop,
    Go;
}

还是使用CASE_TRANSFORMATION策略,并定义策略是capital。

@EnumMapping(nameTransformationStrategy = CASE_TRANSFORMATION, configuration = "capital")
TrafficSignalCapital lowercaseToCapital(TrafficSignalLowercase source);

四、枚举映射的其他用例

还有些场景中,我们需要将枚举映射到其他类型,接下来,一起看看如何处理。

(一)将枚举映射到字符串

定义映射:

@ValueMapping(target = "off", source = "OFF")
@ValueMapping(target = "go", source = "GO")
@ValueMapping(target = "stop", source = "STOP")
String trafficSignalToString(TrafficSignal source);

我们使用@ValueMapping将枚举值映射到字符串,其实是和从字符串转枚举相似的配置逻辑。

(二)将枚举映射到整数或其他数字类型

因为数字类型存在多个构造函数,直接映射到整数可能会导致歧义。可以定义一个具有整数属性的类来解决这个问题。

定义一个包装类:

public class TrafficSignalNumber {
    private Integer number;
}

使用默认方法将枚举映射到整数:

@Mapping(target = "number", source = ".")
TrafficSignalNumber trafficSignalToTrafficSignalNumber(TrafficSignal source);

生成的代码是:

public TrafficSignalNumber trafficSignalToTrafficSignalNumber(TrafficSignal source) {
    if ( source == null ) {
        return null;
    }

    TrafficSignalNumber trafficSignalNumber = new TrafficSignalNumber();

    if ( source != null ) {
        trafficSignalNumber.setNumber( source.ordinal() );
    }

    return trafficSignalNumber;
}

五、处理未知枚举值

前面提到过,在处理枚举值值,当有未映射的枚举值时,MapStruct会抛出异常。

不过,很多时候,当映射失败的时候,我们需要有不同的操作,比如:设置默认值、设置空值、抛出异常等。

(一)未映射抛出异常

抛出异常是默认行为,前面的示例中都是属于这种类型。

(二)映射剩余属性

比如,我们有一个简单的交通信号枚举:

public enum SimpleTrafficSignal {
    OFF,
    ON;
}

需要将toSimpleTrafficSignal映射到SimpleTrafficSignal,但是MapStruct要求所有枚举值都需要映射,不能遗漏,所以我们可以这样写:

@ValueMapping(target = "OFF", source = "OFF")
@ValueMapping(target = "OFF", source = "STOP")
@ValueMapping(target = "ON", source = "GO")
SimpleTrafficSignal toSimpleTrafficSignal(TrafficSignal source);

我们显式地将STOP和OFF都映射到OFF,但是如果值特别多的时候,这样写就显得很傻,我们可以使用ANY_REMAINING配置:

@ValueMapping(target = "ON", source = "GO")
@ValueMapping(target = "OFF", source = ANY_REMAINING)
SimpleTrafficSignal toSimpleTrafficSignalWithRemaining(TrafficSignal source);

生成的代码是:

public SimpleTrafficSignal toSimpleTrafficSignalWithRemaining(TrafficSignal source) {
    if ( source == null ) {
        return null;
    }

    SimpleTrafficSignal simpleTrafficSignal;

    switch ( source ) {
        case GO: simpleTrafficSignal = SimpleTrafficSignal.ON;
        break;
        case OFF: simpleTrafficSignal = SimpleTrafficSignal.OFF;
        break;
        default: simpleTrafficSignal = SimpleTrafficSignal.OFF;
    }

    return simpleTrafficSignal;
}

也就是,除了GO明确映射外,其他的都映射为OFF。

(三)映射未映射的属性

我们可以所有未映射到值全部映射为指定的枚举,比如,所有没有配置的都映射为OFF,我们可以使用ANY_UNMAPPED配置:

@ValueMapping(target = "ON", source = "GO")
@ValueMapping(target = "OFF", source = ANY_UNMAPPED)
SimpleTrafficSignal toSimpleTrafficSignalWithUnmapped(TrafficSignal source);

生成的代码是:

@Override
public SimpleTrafficSignal toSimpleTrafficSignalWithUnmapped(TrafficSignal source) {
    if ( source == null ) {
        return null;
    }

    SimpleTrafficSignal simpleTrafficSignal;

    switch ( source ) {
        case GO: simpleTrafficSignal = SimpleTrafficSignal.ON;
        break;
        default: simpleTrafficSignal = SimpleTrafficSignal.OFF;
    }

    return simpleTrafficSignal;
}

(四)处理空值

MapStruct可以使用NULL关键字处理空的源和空的目标。

假设我们需要将空输入映射到OFF,将GO映射到ON,将任何其他未映射的值映射到空。

我们可以这样定义映射:

@ValueMapping(target = "OFF", source = NULL)
@ValueMapping(target = "ON", source = "GO")
@ValueMapping(target = NULL, source = MappingConstants.ANY_UNMAPPED)
SimpleTrafficSignal toSimpleTrafficSignalWithNullHandling(TrafficSignal source);

生成代码是:

public SimpleTrafficSignal toSimpleTrafficSignalWithNullHandling(TrafficSignal source) {
    if ( source == null ) {
        return SimpleTrafficSignal.OFF;
    }

    SimpleTrafficSignal simpleTrafficSignal;

    switch ( source ) {
        case GO: simpleTrafficSignal = SimpleTrafficSignal.ON;
        break;
        default: simpleTrafficSignal = null;
    }

    return simpleTrafficSignal;
}

(五)指定值抛出异常

还有一种场景,就是为空或者未映射时,抛出异常,我们可以使用THROW_EXCEPTION策略:

定义映射:

@ValueMapping(target = "ON", source = "GO")
@ValueMapping(target = MappingConstants.THROW_EXCEPTION, source = MappingConstants.ANY_UNMAPPED)
@ValueMapping(target = MappingConstants.THROW_EXCEPTION, source = MappingConstants.NULL)
SimpleTrafficSignal toSimpleTrafficSignalWithExceptionHandling(TrafficSignal source);

生成的代码是:

@Override
public SimpleTrafficSignal toSimpleTrafficSignalWithExceptionHandling(TrafficSignal source) {
    if ( source == null ) {
        throw new IllegalArgumentException( "Unexpected enum constant: " + source );
    }

    SimpleTrafficSignal simpleTrafficSignal;

    switch ( source ) {
        case GO: simpleTrafficSignal = SimpleTrafficSignal.ON;
        break;
        default: throw new IllegalArgumentException( "Unexpected enum constant: " + source );
    }

    return simpleTrafficSignal;
}

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2025-01-16 00:00:00

MapStruct映射

2011-10-08 15:49:52

Java

2023-07-07 08:17:48

2025-01-13 00:00:00

MapStruct继承关系Java

2013-01-07 10:14:06

JavaJava枚举

2011-10-10 17:15:52

Java

2021-09-22 12:56:19

编程技能Golang

2025-01-10 00:00:00

MapStructArticlePerson

2024-05-20 12:00:00

Python列表推导式

2025-01-24 00:00:00

MapStruct子类型Mapper

2017-07-24 10:15:32

2023-05-22 08:03:28

JavaScrip枚举定义

2020-09-25 08:00:57

Kubernetes

2010-12-14 09:27:44

绿色网络

2020-09-10 07:00:00

人工智能AI机器学习

2011-10-24 09:25:54

苹果Siri调戏

2022-05-10 08:08:01

find命令Linux

2010-09-01 14:56:14

2011-11-25 10:25:27

SpringJava

2010-03-12 16:23:56

宽带无线接入
点赞
收藏

51CTO技术栈公众号