Я работаю над задачей по созданию API для свойств строки приложения, которые могут быть представлены как Enum, Int, String и, возможно, еще несколько типов.
Итак, я попытался написать такой класс:
object Configuration {
inline fun <reified T> get(context: Context, propertyName: String): T {
val propertyStringValue = context.config[propertyName]
?: throw InvalidConfigurationException("Unable to access property $propertyName")
if (T::class.isSubclassOf(Enum::class)) {
// how can I convert it to enum by it's class name?
} else if (T::class.isSubclassOf(String::class)) {
// convert to string
} else if (T::class.isSubclassOf(Integer::class)) {
// convert it integer
}
}
}
enum class BuildingLevel{
FIRST,
SECOND,
...
}
enum class StreetType {
OPEN,
ISOLATED,
...
}
Учитывая, что имя свойства — STREET_TYPE
и context.config["STREET_TYPE"]
return OPEN
, я ожидаю, что Configuration.get<StreetType>("STREET_TYPE")
вернет запись перечисления StreetType.OPEN
и Configuration.get<String>("STREET_TYPE")
вернет OPEN
строку.
Что мне делать в ветке enum
if, если я хочу преобразовать строку в запись перечисления, имеющую reified дженерики, представляющие определенный класс перечисления?
Какое свойство какого объекта вы используете для определения класса?
извините, ребята, я недостаточно конкретно объяснил, что мне нужно. Пожалуйста, смотрите отредактированный вопрос для более подробной информации.
inline fun <reified T : Enum<T>> getEnumValue(name: String): T? =
try {
enumValueOf<T>(name) // Retrieves the enum constant of the specified name
} catch (e: IllegalArgumentException) {
null // Returns null if the name doesn't match any enum constants
}
}
вам нужно изменить свой T как подтип Enum, чтобы вы могли повторно использовать функцию enumValueOf.
Я знаю, что могу это сделать, но T
также может быть строкой, int, double и т. д., и я не хочу иметь конкретный метод для свойств перечисления и отдельный метод для остальных.
Понятно, я пытался переписать логику enumValueOf, все еще работает. если получу, прокомментирую.
Я не понимаю, как String().toDouble()
можно вернуть как T
, а T
есть <reified T : Enum<T>>
. Можете ли вы обновить свой ответ, указав код, который вам подходит?
Это один из случаев, когда принудительная строгая типизация приносит больше вреда, чем пользы. Мы знаем, что T
— это перечисление, однако невозможно «привести» параметр типа или добавить к нему верхние границы. Таким образом, даже если мы знаем, что вызов enumValueOf<T>(propertyStringValue)
должен быть правильным, компилятор не позволяет нам это сделать.
Для упрощения можно сказать, что задача состоит в реализации этой функции:
inline fun <reified T> untypedEnumValueOf(name: String): T = ???
Я считаю, что с Kotlin 2.0.20 нет чистого способа реализовать это. Однако есть некоторые хаки.
Во-первых, в Котлине мы можем подавлять многие не только предупреждения, но даже ошибки:
inline fun <reified T> untypedEnumValueOf(name: String): T {
@Suppress("UPPER_BOUND_VIOLATED")
return enumValueOf<T>(name)
}
К сожалению, подавляя ошибки, мы попадаем в страну драконов. Может скомпилироваться, а может и нет. На самом деле этот код корректно работал в Kotlin 1.x, но он больше не компилируется в 2.x с каким-то глупым сообщением о том, что String
не может быть String
. Вероятно, это всего лишь ошибка, и она будет исправлена в будущих версиях Kotlin. Мы даже можем утверждать, что это решение самое чистое, поскольку мы просто делаем то, что должны, и говорим компилятору, чтобы он не беспокоил нас своими глупыми ограничениями. Но это не работает.
Аналогичным образом мы можем обойти описанную выше проблему, используя enumEntries
/enumValues
вместо enumValueOf
. Таким образом мы избегаем передачи параметра String
, который изначально вызвал проблему:
inline fun <reified T> untypedEnumValueOf(name: String): T {
@Suppress("UPPER_BOUND_VIOLATED")
return enumEntries<T>().single { (it as Enum<*>).name == name }
}
Этот код работает в Kotlin 2.x, но это замысловатый вариант первого. Кроме того, существует риск, что он перестанет работать в будущих версиях компилятора Kotlin и/или для конкретных целей компиляции, как и в первом решении.
Наконец, мы можем использовать API отражения. Вероятно, это медленнее и не так элегантно, как простое получение перечисления по имени, но это должен быть довольно надежный подход:
inline fun <reified T> untypedEnumValueOf(name: String): T {
return T::class.staticFunctions
.single { it.name == "valueOf" }
.call(name) as T
}
Ух ты, это на удивление не так просто, как я думал. Огромное спасибо за разъяснения и возможные решения!
@mr.nothing Да, к сожалению, вам не повезло попасть в один из этих редких крайних случаев, которые плохо поддерживаются языком/компилятором. Я не думаю, что есть реальная причина не поддерживать это. Это всего лишь ограничение или отсутствующая функция в текущей версии Kotlin.
вы хотите преобразовать свойствоStringValue в перечисление?