1. 概述

MapStruct1 是一个高效且类型安全的库,它简化了Java对象之间的数据映射,消除了手动转换逻辑的需求。

在这个教程中,我们将探讨如何使用MapStruct将枚举(enum) 映射到字符串。

2. 枚举到字符串的映射

在与外部API的数据交换中,使用Java枚举作为字符串而非序号可以简化操作,使得数据获取更方便,UI可读性也更强。

假设我们想将DayOfWeek枚举转换为字符串。

DayOfWeek是Java 日期时间API 2 中的一个枚举,表示一周中的七天,从周一到周日。

现在让我们实现MapStruct映射器:

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

    String toString(DayOfWeek dayOfWeek);

    // additional mapping methods as needed
}

DayOfWeekMapper接口是一个MapStruct映射器,由@Mapper注解标记。我们定义了一个toString()方法,它接受一个DayOfWeek枚举并将其转换为字符串表示。默认情况下,MapStruct使用name()方法获取枚举的字符串值

class DayOfWeekMapperUnitTest {
    private DayOfWeekMapper dayOfWeekMapper = DayOfWeekMapper.INSTANCE;

    @ParameterizedTest
    @CsvSource({"MONDAY,MONDAY", "TUESDAY,TUESDAY", "WEDNESDAY,WEDNESDAY", "THURSDAY,THURSDAY",
                "FRIDAY,FRIDAY", "SATURDAY,SATURDAY", "SUNDAY,SUNDAY"})
    void whenDayOfWeekMapped_thenGetsNameString(DayOfWeek source, String expected) {
        String target = dayOfWeekMapper.toString(source);
        assertEquals(expected, target);
    }
}

这验证了toString()方法将DayOfWeek枚举值映射为其预期的字符串名称。这种形式的参数化测试(/parameterized-tests-junit-5)也让我们可以从一个测试中测试所有可能的变异。

2.1. 处理null

现在来看看MapStruct如何处理null默认情况下,MapStruct将null源映射到null目标。但是,这种行为可以修改。

立即验证映射器对于null输入返回null结果:

@Test
void whenNullDayOfWeekMapped_thenGetsNullResult() {
    String target = dayOfWeekMapper.toString(null);
    assertNull(target);
}

MapStruct提供了MappingConstants.NULL来管理null值:

@Mapper
public interface DayOfWeekMapper {

    @ValueMapping(target = "MONDAY", source = MappingConstants.NULL)
    String toStringWithDefault(DayOfWeek dayOfWeek);
}

对于null值,这个映射返回默认值MONDAY

@Test
void whenNullDayOfWeekMappedWithDefaults_thenReturnsDefault() {
    String target = dayOfWeekMapper.toStringWithDefault(null);
    assertEquals("MONDAY", target);
}

3. 字符串到枚举的映射

现在,让我们看看映射方法,将字符串转换回枚举:

@Mapper
public interface DayOfWeekMapper {

    DayOfWeek nameStringToDayOfWeek(String day);
}

这个映射器将表示一周中某一天的字符串转换为相应的DayOfWeek枚举值:

@ParameterizedTest
@CsvSource(
        {"MONDAY,MONDAY", "TUESDAY,TUESDAY", "WEDNESDAY,WEDNESDAY", "THURSDAY,THURSDAY",
         "FRIDAY,FRIDAY", "SATURDAY,SATURDAY", "SUNDAY,SUNDAY"})
void whenNameStringMapped_thenGetsDayOfWeek(String source, DayOfWeek expected) {
    DayOfWeek target = dayOfWeekMapper.nameStringToDayOfWeek(source);
    assertEquals(expected, target);
}

我们验证了nameStringToDayOfWeek()方法将一天的字符串表示映射为其对应的枚举。

3.1. 处理未映射的值

如果字符串不匹配枚举的name或其他常量,MapStruct会抛出错误。通常,这是为了确保所有值都能安全、可预测地映射。**MapStruct创建的映射方法在遇到未识别的源值时会抛出IllegalStateException**:

@Test
void whenInvalidNameStringMapped_thenThrowsIllegalArgumentException() {
    String source = "Mon";
    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
        dayOfWeekMapper.nameStringToDayOfWeek(source);
    });
    assertTrue(exception.getMessage().equals("Unexpected enum constant: " + source));
}

要改变这种行为,MapStruct还提供了MappingConstants.ANY_UNMAPPED这指示MapStruct将任何未映射的源值映射到目标常量值

@Mapper
public interface DayOfWeekMapper {

    @ValueMapping(target = "MONDAY", source = MappingConstants.ANY_UNMAPPED)
    DayOfWeek nameStringToDayOfWeekWithDefaults(String day);
}

最后,这个@ValueMapping注解设定了未映射源的默认行为。因此,任何未映射的输入默认为MONDAY

@ParameterizedTest
@CsvSource({"Mon,MONDAY"})
void whenInvalidNameStringMappedWithDefaults_thenReturnsDefault(String source, DayOfWeek expected) {
    DayOfWeek target = dayOfWeekMapper.nameStringToDayOfWeekWithDefaults(source);
    assertEquals(expected, target);
}

4. 枚举到自定义字符串的映射

现在,我们也让枚举转换为DayOfWeek的自定义短表示,如“Mon”,“Tue”等:

@Mapper
public interface DayOfWeekMapper {

    @ValueMapping(target = "Mon", source = "MONDAY")
    @ValueMapping(target = "Tue", source = "TUESDAY")
    @ValueMapping(target = "Wed", source = "WEDNESDAY")
    @ValueMapping(target = "Thu", source = "THURSDAY")
    @ValueMapping(target = "Fri", source = "FRIDAY")
    @ValueMapping(target = "Sat", source = "SATURDAY")
    @ValueMapping(target = "Sun", source = "SUNDAY")
    String toShortString(DayOfWeek dayOfWeek);
}

相反,这个toShortString()映射配置使用@ValueMappingDayOfWeek枚举转换为缩写字符串:

@ParameterizedTest
@CsvSource(
        {"MONDAY,Mon", "TUESDAY,Tue", "WEDNESDAY,Wed", "THURSDAY,Thu",
         "FRIDAY,Fri", "SATURDAY,Sat", "SUNDAY,Sun"})
void whenDayOfWeekMapped_thenGetsShortString(DayOfWeek source, String expected) {
    String target = dayOfWeekMapper.toShortString(source);
    assertEquals(expected, target);
}

5. 自定义字符串到枚举的映射

最后,我们将看到如何将缩写字符串转换为DayOfWeek枚举:

@Mapper
public interface DayOfWeekMapper {

    @InheritInverseConfiguration(name = "toShortString")
    DayOfWeek shortStringToDayOfWeek(String day);
}

此外,**@InheritInverseConfiguration注解定义了一个反向映射**,这使得shortStringToDayOfWeek()可以从toShortString()方法继承其配置,将缩写的日子名称转换为相应的DayOfWeek枚举:

@ParameterizedTest
@CsvSource(
        {"Mon,MONDAY", "Tue,TUESDAY", "Wed,WEDNESDAY", "Thu,THURSDAY",
         "Fri,FRIDAY", "Sat,SATURDAY", "Sun,SUNDAY"})
void whenShortStringMapped_thenGetsDayOfWeek(String source, DayOfWeek expected) {
    DayOfWeek target = dayOfWeekMapper.shortStringToDayOfWeek(source);
    assertEquals(expected, target);
}

6. 总结

在这篇文章中,我们学习了如何使用MapStruct的@ValueMapping注解进行枚举到字符串的映射。我们也使用了@InheritInverseConfiguration来保持双向映射的一致性。利用这些技巧,我们可以顺利处理枚举到字符串的转换,并保持代码清晰、易于维护。

如往常一样,本文的所有完整代码示例可在GitHub上找到3


  1. MapStruct: https://mapstruct.org/
  2. Java 8日期时间API: https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html
  3. GitHub代码示例: https://github.com/eugenp/tutorials/tree/master/mapstruct