Kotlin通用字符串转任意类型函数的枚举转换编译报错问题
问题背景
我在开发一个通用类型转换函数,目标是将任意输入字符串转换为指定的目标数据类型,预期效果如下:
convert<Int>("23") => 23 convert<LocalDateTime>("2022-06-29T18:02:00") => 对应LocalDateTime实例 convert<String>("hg") => "hg"
转换无法完成时需要抛出错误,初始版本未实现完整错误处理,代码如下:
/** * 将字符串转换为指定类型,转换失败抛出错误 */ inline fun <reified T> stringToTypeOrError(value: String): T { val v: T = when (T::class) { Int::class -> value.toInt() as T String::class -> value as T Double::class -> value.toDouble() as T LocalDateTime::class -> LocalDateTime.parse(value) as T else -> throw BadRequestException("不支持的转换值: $value.") } return v }
遇到的问题
新增字符串转枚举类型支持时,需要实现类似convert<Order>("asc") => Order.ASC的转换效果,要求无需为每个枚举类单独添加when分支即可支持所有枚举类型转换,我编写了如下实现:
enum class Order { ASC, DESC } /** * 字符串转枚举,转换失败抛自定义错误 */ inline fun <reified E : Enum<E>> enumValueOrError(value: String): E { return try { enumValueOf(value) } catch (e: IllegalArgumentException) { throw BadRequestException("不支持的转换值: $value.") } } /** * 将字符串转换为指定类型,转换失败抛出错误 */ inline fun <reified T> stringToTypeOrError(value: String): T { val v: T = when (T::class) { Int::class -> value.toInt() as T String::class -> value as T Double::class -> value.toDouble() as T LocalDateTime::class -> LocalDateTime.parse(value) as T else -> { if (T::class.javaClass.isEnum) { return enumValueOrError<T>(value) } else { throw NotImplementedError("未实现该类型的转换:${T::class.qualifiedName}") } } } return v }
构建时在return enumValueOrError<T>(value)行抛出编译错误:
Type argument is not within its bounds: should be subtype of 'Enum<TypeVariable(E)>'
需要解决两个问题:
- 修复上述编译错误
- 在不针对每个枚举类单独添加when分支的前提下,将泛型T正确传入
enumValueOrError完成枚举转换
解决方案
错误根因
- 泛型上界不匹配:
enumValueOrError要求传入的泛型参数必须是Enum<E>的子类型,但stringToTypeOrError的泛型T没有声明这个上界,编译器无法在编译期证明T是枚举类型,哪怕运行时做了类型判断,编译期类型检查依然不通过。 - 枚举判断逻辑错误:
T::class.javaClass.isEnum判断的是KClass对象本身的类型,永远返回false,正确的枚举判断应该取T对应的Java类再判断isEnum属性。
修复后代码
在运行时判断T为枚举类型后,通过安全的强制类型转换满足泛型上界要求即可,保留原有enumValueOrError函数的修复版本如下:
inline fun <reified T> stringToTypeOrError(value: String): T { val targetClass = T::class return when (targetClass) { Int::class -> value.toInt() as T String::class -> value as T Double::class -> value.toDouble() as T LocalDateTime::class -> LocalDateTime.parse(value) as T else -> { if (targetClass.java.isEnum) { // 前面已经做了运行时枚举判断,这里的转换是安全的,消除unchecked警告 @Suppress("UNCHECKED_CAST") return enumValueOrError<T as Enum<T>>(value.uppercase()) as T } else { throw NotImplementedError("未实现该类型的转换: ${targetClass.qualifiedName}") } } } }
如果不想保留单独的枚举转换函数,也可以直接调用JDK原生枚举转换方法,代码更简洁:
inline fun <reified T> stringToTypeOrError(value: String): T { val targetClass = T::class return when (targetClass) { Int::class -> value.toInt() as T String::class -> value as T Double::class -> value.toDouble() as T LocalDateTime::class -> LocalDateTime.parse(value) as T else -> { if (targetClass.java.isEnum) { @Suppress("UNCHECKED_CAST") java.lang.Enum.valueOf(targetClass.java as Class<out Enum<*>>, value.uppercase()) as T } else { throw NotImplementedError("未实现该类型的转换: ${targetClass.qualifiedName}") } } } }
实现说明
- 修正了枚举判断逻辑:用
targetClass.java.isEnum判断目标类型是否为枚举,替代原来错误的判断方式 - 运行时已经确认T是枚举类型,因此强制类型转换不存在类型安全问题,用
@Suppress("UNCHECKED_CAST")消除编译警告即可 - 枚举转换时加
value.uppercase()可以兼容小写传入的场景,比如传入"asc"也能正确匹配Order.ASC - 该实现无需为每个枚举单独添加when分支,所有枚举类型都可以自动走统一的转换逻辑
内容的提问来源于stack exchange,提问作者Biswajit
相关产品推荐
相关产品推荐

