Вы не можете этого сделать.
Оператор распространения Kotlin (*
) ограничен расширением массива Vararg.Из документов :
Когда мы вызываем vararg
-функцию, мы можем передавать аргументы один за другим, например, asList(1, 2, 3)
, или, если у нас уже естьмассив и хотим передать его содержимое в функцию, мы используем оператор распространения (префикс массива с *
):
Это ограничивает использование вами оператора распространения.
Кроме того, инициализация на основе скобок не работает, поскольку вместо этого создается функция:
interface Control {
var id: String?
var width: Number?
var height: Number?
}
class TextBoxControl {
var id: String? = null
var width: Number? = null
var height: Number? = null
var text: String? = null
}
fun createControl() : TextBoxControl {
return {};
}
Ошибка: (17, 11) Несоответствие типов: выведенный тип is ()-> Единица, но ожидался TextBoxControl
TL; DR:
Вы не можете инициализировать его с помощью {}
.Вы не можете использовать оператор распространения для получения аргументов.
Есть несколько способов решить эту проблему без использования конструкторов, но ни один из них не является гибким (без углубления в размышления, но это падение производительности).
Прежде всеготем не менее, я весьма предлагаю вам предпочитать конструкторыВ Kotlin гораздо проще разобраться:
interface Control {
var id: String?
var width: Number?
var height: Number?
}
// Created just to have something to pass as options
class ControlImpl() : Control {
override var id: String? = "Hello World!"
override var width: Number? = 314159
override var height: Number? = 271
}
data class TextBoxControl(var id: String? = null,
var width: Number? = null,
var height: Number? = null,
var text: String? = null)
fun buildTextBoxControl(dataFromDb: String, options: Control) : TextBoxControl {
return TextBoxControl(id = options.id, width = options.width, height = options.width, text = dataFromDb /*Not entirely what you had, but this is just for the sake of my answer*/)
}
fun main() {
var control = buildTextBoxControl("String", ControlImpl().apply {
id = "id"
width = 42
height = 42
})
println(control)
}
TextBoxControl (id = id, width = 42, height = 42, text = String)
Этозначительно проще, чем альтернативные подходы, и, вероятно, также более эффективно использует память, поскольку у Kotlin нет встроенной поддержки желаемого синтаксиса.
Примечание: Если вы абсолютно не представляете, какие переменные вы можете получить из объекта управления, и вам все еще нужно использовать все, я настоятельно рекомендую вамперепроектируйте свой код.Ни Kotlin, ни Java не предназначены для «Я дам вам кое-что во время выполнения, и вы должны это выяснить».Самое близкое, что вы получаете к этому - это отражение, но оно все еще ограничено полями, которые у вас есть под рукой.
Если, однако, вы хотите, чтобы не знали, какие поля у вас есть, а какие нет, используйте вместо этого карту.
В противном случае, и если вы действительно не хотите использовать конструкторы, у вас есть два варианта:
Установка полей вручную
Если проблема в преобразовании, вы можете броситьв функции для его преобразования:
fun Control.toTextBoxControl() : TextBoxControl{
return TextBoxControl().let { it ->
it.id = this.id
it.width = this.width
it.height = this.height
it
}
}
Теперь вы можете относительно легко преобразовать элемент управления в переопределенный тип:
fun buildTextBoxControl(dataFromDb: String, options: Control) : TextBoxControl {
return options.toTextBoxControl()
}
Но как насчет дополнительных аргументов?Они были проигнорированы до сих пор.
Если вы на самом деле хотите пойти без конструкторов, вы можете использовать operator fun invoke
для изменения параметров:
class TextBoxControl {
var id: String? = null
var width: Number? = null
var height: Number? = null
var text: String? = null
operator fun invoke(id: String? = null, width: Number? = null, height: Number? = null, text: String? = null)
: TextBoxControl{
if (id != null) this.id = id;
if (width != null) this.width = width;
if (height != null) this.height = height;
if (text != null) this.text = text;
return this
}
override fun toString() : String {
return "TextBoxControl(id=$id, width: $width=height=$height, text=$text)"
}
}
Не совсем красиво.Я также столкнулся с проблемой дизайна - я не могу найти достойный способ разрешить использование null с помощью invoke.Если нет нулевой проверки, все исходные переменные будут переопределены.
В любом случае, это позволяет вам изменить функцию преобразования и функцию создания:
fun Control.toTextBoxControl() : TextBoxControl{
return TextBoxControl()(id = id, width = width, height = height)
}
fun buildTextBoxControl(dataFromDb: String, options: Control) : TextBoxControl {
return options.toTextBoxControl()(text = dataFromDb)
}
Если вы не знаете, что означает operator fun invoke
, это синтаксический сахар для вызова объекта.Так что если у вас есть экземпляр объекта, который имеет operator fun invoke()
, вы можете написать someInstanceOfTheClass()
.Он может иметь аргументы и тип возвращаемого значения, как вы считаете нужным.Вы можете узнать больше о перегрузке операторов в Kotlin здесь .
Отражение
ВНИМАНИЕ: Отражение сильно сказывается на производительности.См. этот вопрос для подробностей об этом.
Бросая карту в интерфейс управления (который возвращает значения в виде карты - также неэффективно из-за нехватки памяти и является еще одной причиной для использования конструкторов), вы можете перебирать карту и использовать отражение для установки значений:
interface Control {
var id: String?
var width: Number?
var height: Number?
fun toMap() = mutableMapOf("id" to id, "width" to width, "height" to height) as MutableMap<String, Any>
}
class ControlImpl() : Control {
override var id: String? = "Hello World!"
override var width: Number? = 314159
override var height: Number? = 271
}
class TextBoxControl {
var id: String? = null
var width: Number? = null
var height: Number? = null
var text: String? = null
// this could also be a static function called `createTextBoxControlFromMap`, non-static function, or thrown into an apply block or generic extension function
operator fun invoke(map: Map<String, Any>) : TextBoxControl {
for((k, v) in map) {
val f = this::class.java.getDeclaredField(k)
f.setAccessible(true)
f.set(this, v)
}
return this;
}
}
fun buildTextBoxControl(dataFromDb: String, options: Control) : TextBoxControl {
return TextBoxControl()(options.toMap().apply {
put("text", dataFromDb)
})
}
fun main() {
var control = buildTextBoxControl("String", ControlImpl().apply {
id = "id"
width = 42
height = 42
})
println(control)
}
Но, опять же, , пожалуйста, подумайте об использовании конструкторов .