Что осталось за кадром?
В некоторых случаях необходимо определить функцию с переменным числом параметров; Вот пара примеров, с которыми вы, вероятно, сталкивались в Android:printf(String format, Obj... args)
или execute(Params... params)
из AsyncTask.
Для начала разберем основы vararg и оператора spread, а затем подробно рассмотрим несколько сценариев, анализируя, что происходит за кадром.
vararg в Kotlin
Kotlin поддерживает объявление функции с переменным числом аргументов. Достаточно поставить перед названием параметра модификатор vararg: fun format(format: String, vararg args: Any)
.
Несколько правил касательно vararg
В Java параметр vararg должен быть последним в списке параметров — поэтому параметр vararg может быть только один. В Kotlin vararg не обязательно должен занимать последнее место в списке, однако использование нескольких параметров vararg все же недопустимо.
Рассмотрим декомпилированный исходный код Java при объявлении параметров vararg.
Параметр vararg в качестве последнего в списке
Объявление параметра vararg в качестве последнего в списке в функции Kotlin выглядит следующим образом:
fun format(format: String, vararg params: String)
во время компиляции в соответствующий код Java:
void format(@NotNull String format, @NotNull String... params)
Объявление параметров после vararg
Параметр vararg не является последним в списке:
fun format(format: String, vararg params: String, encoding: String)
при компиляции в соответствующий код Java:
void format(String format, String[] params, String encoding)
Вывод: если vararg не является последним параметром, то он компилируется в качестве массива типа parameter.
Оператор Spread
“Вау!? В Kotlin есть указатели?” — моя первая реакция при виде подобной функции вKotlin: format(output, *params)
.
Нет, это не указатель. Это spread — оператор, который распаковывает массив в список значений из этого массива. Он нужен при передаче массива в качестве параметра vararg.
При вызове функции format
, определенной следующим образом:
val params = arrayOf("param1", "param2")
format(output, params)
происходит ошибка компиляции Type mismatch: inferred type is Array<String> but String was expected
.
Для того, чтобы ее исправить, нужно использовать оператор spread для распаковки массива параметров в соответствующие значения: format(output, *params)
Рассмотрим соответствующий декомпилированный код Java:
String[] params = new String[]{"param1", "param2"};
format(output, (String[])Arrays.copyOf(params, params.length));
Объединение значений spread
При использовании оператора spread можно передать дополнительные значения в параметр vararg или даже объединить несколько массивов spread в одном параметре:
val params = arrayOf("param1", "param2")
val opts = arrayOf("opts1", "opts2", "opts3")
format(output, *params, "additional", *opts)
Декомпилированный код Java:
String[] params = new String[]{"param1", "param2"};
String[] opts = new String[]{"opts1", "opts2", "opts3"};
SpreadBuilder var10002 = new SpreadBuilder(3);
var10002.addSpread(params);
var10002.add("additional");
var10002.addSpread(opts);
this.format(output, (String[])var10002.toArray(new String[var10002.size()]));
Рассмотрим определение arrayOf()
:
public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>
Это означает, что оператор spread можно использовать для объединения массивов в новый массив:
val params = arrayOf("param1", "param2")
val opts = arrayOf("opts1", "opts2", "opts3")
val allParams = arrayOf(*params, *opts)
Как и ожидалось, декомпилированный код Java выглядит так:
String[] params = new String[]{"param1", "param2"};
String[] opts = new String[]{"opts1", "opts2", "opts3"};
SpreadBuilder var10000 = new SpreadBuilder(2);
var10000.addSpread(params);
var10000.addSpread(opts);
String[] allParams = (String[])var10000.toArray(new String[var10000.size()]);
Вывод: во время объединения значений spread в параметр vararg, за кадром используется SpreadBuilder для сбора всех значений. Затем SpreadBuilder конвертируется в массив при вызове функции.
Мы разобрали, как Kotlin обрабатывает параметры vararg, и как выглядит соответствующий код Java при объявлении и вызове подобных функций.
Перевод статьи Tibi Csabai: Kotlin’s vararg and spread operator