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()
映射配置使用@ValueMapping
将DayOfWeek
枚举转换为缩写字符串:
@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。
- MapStruct: https://mapstruct.org/
- Java 8日期时间API: https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html
- GitHub代码示例: https://github.com/eugenp/tutorials/tree/master/mapstruct