Kotlin

Что осталось за кадром?


В некоторых случаях необходимо определить функцию с переменным числом параметров; Вот пара примеров, с которыми вы, вероятно, сталкивались в Android:printf(String format, Obj... args)или execute(Params... params) из AsyncTask.

Для начала разберем основы vararg и оператора spread, а затем подробно рассмотрим несколько сценариев, анализируя, что происходит за кадром.

Skillbox

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