Kotlin Array в Java varargs: как не сломать совместимость и написать чистый код
Вы когда-нибудь писали на Kotlin, вызывали старый добрый Java-метод с varargs — и вдруг получали ошибку компиляции? Если да, то вы не одиноки. Вопрос "Convert Kotlin Array to Java varargs" на Stack Overflow набрал тысячи просмотров. И это неудивительно: на стыке двух языков часто возникают неочевидные нюансы.
В этой статье мы разберём, как правильно передать массив Kotlin в Java-метод с переменным числом аргументов. Вы узнаете, что такое spread-оператор, почему Array<String> не равен String... и как избежать типичных ошибок. Поехали!
Что такое varargs в Java и зачем они нужны
Для начала вспомним основы. Varargs (variable-length arguments) — это механизм Java, который позволяет передавать в метод произвольное количество аргументов одного типа. Синтаксически это обозначается троеточием после типа:
public void printMessages(String... messages) {
for (String msg : messages) {
System.out.println(msg);
}
}
Вы можете вызвать этот метод так:
printMessages("Hello");
printMessages("Hello", "World");
printMessages(); // пустой вызов тоже работает
Внутри метода аргументы представлены как массив String[]. Но снаружи — это просто набор значений, разделённых запятыми. Удобно, правда?
А теперь представьте, что у вас есть Kotlin Array, и вы хотите передать его в такой Java-метод. Наивный код не сработает:
val messages = arrayOf("Hello", "World")
printMessages(messages) // Ошибка компиляции!
Почему? Потому что Kotlin строже относится к типам. Java-метод ожидает String..., а вы передаёте Array<String>. Это разные вещи. Компилятор видит несоответствие и выдаёт ошибку.
Решение: spread-оператор (оператор *)
В Kotlin для таких случаев существует spread-оператор — звёздочка * перед массивом. Он буквально «разворачивает» массив в отдельные элементы, которые затем передаются как varargs.
val messages = arrayOf("Hello", "World")
printMessages(*messages) // Работает!
Всё гениальное просто. Но давайте разберёмся, как это работает под капотом.
Как spread-оператор преобразует типы
Когда вы пишете *messages, Kotlin:
- Берёт каждый элемент массива
- «Вытаскивает» их из обёртки
Array - Передаёт как отдельные аргументы в Java-метод
Для Java-метода это выглядит так, как будто вы написали printMessages("Hello", "World"). Никаких массивов — только чистые строки.
Важный нюанс: spread-оператор работает только с Array (и некоторыми другими коллекциями через toTypedArray()). Обычный List так не развернуть.
Типичные ошибки и как их избежать
Даже зная про spread-оператор, новички часто допускают ошибки. Вот самые частые из них.
Ошибка 1: Передача массива без звездочки
// Неправильно:
val numbers = intArrayOf(1, 2, 3)
someJavaMethod(numbers) // Ошибка
// Правильно:
someJavaMethod(*numbers.toTypedArray())
Заметьте: для примитивных типов (IntArray, DoubleArray и т.д.) нужно сначала преобразовать в Array через .toTypedArray(). Spread-оператор работает только с объектными массивами.
Ошибка 2: Путаница с List
Многие пытаются передать List<String> напрямую:
val list = listOf("A", "B")
printMessages(*list) // Ошибка! List != Array
Решение: сначала преобразуйте список в массив:
printMessages(*list.toTypedArray())
Ошибка 3: Изменение порядка аргументов
Spread-оператор можно использовать вместе с обычными аргументами, но только в конце списка (как и varargs в Java):
// Можно:
printMessages("First", *moreMessages)
// Нельзя:
printMessages(*moreMessages, "Last") // Ошибка компиляции
Практический пример: смешиваем Kotlin и Java
Давайте соберём всё вместе. Предположим, у вас есть Java-класс с методом для форматирования строк:
// Java
public class StringFormatter {
public static String format(String template, String... args) {
return String.format(template, (Object[]) args);
}
}
В Kotlin вы хотите передать динамический список аргументов:
// Kotlin
fun main() {
val template = "Hello, %s! Today is %s."
val words = arrayOf("Alice", "Monday")
val result = StringFormatter.format(template, *words)
println(result) // Hello, Alice! Today is Monday.
}
Обратите внимание: мы передаём *words, а не words. Если забыть звёздочку, компилятор выдаст ошибку: "Type mismatch: inferred type is Array<String> but String was expected".
А если массив пустой? Edge cases
Spread-оператор корректно обрабатывает пустые массивы:
val emptyArray = emptyArray<String>()
printMessages(*emptyArray) // Вызовется с 0 аргументов, всё ок
А что с null? Будьте осторожны:
val nullArray: Array<String>? = null
// printMessages(*nullArray) // NullPointerException!
// Лучше так:
printMessages(*nullArray.orEmpty())
Всегда проверяйте на null перед spread-оператором, иначе получите исключение в рантайме.
Совместимость с Java: что ещё нужно знать
Если вы пишете код на Kotlin, который должны вызывать Java-разработчики, помните: Kotlin-функции с vararg тоже используют spread-оператор при вызове из Java? Нет! Java видит Kotlin-vararg как массив:
// Kotlin
fun greet(vararg names: String) {
names.forEach { println("Hello, $it") }
}
// Из Java:
String[] people = {"Alice", "Bob"};
greet(people); // Работает без spread!
Это ещё один повод помнить о разнице языков: Kotlin защищает от случайной передачи массива, а Java — нет.
Производительность: стоит ли беспокоиться?
Spread-оператор создаёт временную копию массива