Коллекции в Java и Kotlin
Коллекции — это группы переменного числа элементов (возможно, нулевого), которые имеют значение для решаемой задачи и обычно используются вместе. В этом руководстве объясняются и сравниваются понятия и операции с коллекциями в Java и Kotlin. Оно поможет вам перейти с Java на Kotlin и писать код в истинно котлиновском стиле.
Первая часть этого руководства содержит краткий словарь операций над одинаковыми коллекциями в Java и Kotlin. Она разделена на одинаковые операции и операции, существующие только в Kotlin. Во второй части руководства, начиная с раздела Изменяемость, некоторые различия объясняются на конкретных примерах.
Введение в коллекции см. в разделе Обзор коллекций или в этом видео Себастьяна Айгнера, представителя разработчиков Kotlin.
Одинаковые операции в Java и Kotlin
В Kotlin многие операции с коллекциями выглядят точно так же, как их аналоги в Java.
Операции со списками, множествами, очередями и двусторонними очередями
Описание |
Общие операции |
Другие варианты в Kotlin |
|---|---|---|
Добавить один или несколько элементов |
|
Используйте оператор |
Проверить, содержит ли коллекция один или несколько элементов |
|
Используйте ключевое слово |
Проверить, пуста ли коллекция |
|
Используйте |
Удалить элементы по заданному условию |
|
|
Оставить только выбранные элементы |
|
|
Удалить все элементы из коллекции |
|
|
Получить поток из коллекции |
|
В Kotlin есть собственный способ обработки потоков: последовательности и такие методы, как |
Получить итератор коллекции |
|
Операции с отображениями
Описание |
Общие операции |
Другие варианты в Kotlin |
|---|---|---|
Добавить один или несколько элементов |
|
В Kotlin присваивание |
Заменить один или несколько элементов |
|
Используйте оператор индексирования |
Получить элемент |
|
Чтобы получить элемент, используйте оператор индексирования: |
Проверить, содержит ли отображение один или несколько элементов |
|
Используйте ключевое слово |
Проверить, пусто ли отображение |
|
Используйте |
Удалить элемент |
|
Используйте оператор |
Удалить все элементы из отображения |
|
|
Получить поток из отображения |
|
Операции, существующие только для списков
Описание |
Общие операции |
Другие варианты в Kotlin |
|---|---|---|
Получить индекс элемента |
|
|
Получить последний индекс элемента |
|
|
Получить элемент |
|
Чтобы получить элемент, используйте оператор индексирования: |
Получить подсписок |
|
|
Заменить один или несколько элементов |
|
Используйте оператор индексирования вместо |
Операции, которые немного различаются
Операции для любых типов коллекций
Описание |
Java |
Kotlin |
|---|---|---|
Получить размер коллекции |
|
|
Получить плоский доступ к элементам вложенной коллекции |
|
|
Применить заданную функцию к каждому элементу |
|
|
Последовательно применить заданную операцию к элементам коллекции и вернуть накопленный результат |
|
|
Сгруппировать элементы по классификатору и подсчитать их |
|
|
Отфильтровать по условию |
|
|
Проверить, удовлетворяют ли элементы коллекции условию |
|
|
Отсортировать элементы |
|
|
Взять первые N элементов |
|
|
Взять элементы, удовлетворяющие предикату |
|
|
Пропустить первые N элементов |
|
|
Пропустить элементы, удовлетворяющие предикату |
|
|
Создать отображения из элементов коллекции и связанных с ними значений |
|
Чтобы выполнить все перечисленные выше операции с отображениями, сначала нужно получить entrySet отображения.
Операции со списками
Описание |
Java |
Kotlin |
|---|---|---|
Отсортировать список в естественном порядке |
|
|
Отсортировать список в порядке убывания |
|
|
Удалить элемент из списка |
|
|
Заполнить все элементы списка заданным значением |
|
|
Получить уникальные элементы списка |
|
Операции, отсутствующие в стандартной библиотеке Java
zip(),unzip()— преобразовать коллекцию.aggregate()— сгруппировать по условию.takeLast(),takeLastWhile(),dropLast(),dropLastWhile()— взять или пропустить элементы по предикату.slice(),chunked(),windowed()— получить части коллекции.Операторы plus (
+) и minus (-) — добавить или удалить элементы.
Если вы хотите подробно изучить zip(), chunked(), windowed() и некоторые другие операции, посмотрите это видео Себастьяна Айгнера о расширенных операциях с коллекциями в Kotlin:
Изменяемость
В Java существуют изменяемые коллекции:
// Java
// This list is mutable!
public List<Customer> getCustomers() { ... }
Частично изменяемые коллекции:
// Java
List<String> numbers = Arrays.asList("one", "two", "three", "four");
numbers.add("five"); // Fails in runtime with `UnsupportedOperationException`
А также неизменяемые коллекции:
// Java
List<String> numbers = new LinkedList<>();
// This list is immutable!
List<String> immutableCollection = Collections.unmodifiableList(numbers);
immutableCollection.add("five"); // Fails in runtime with `UnsupportedOperationException`
Если написать два последних фрагмента кода в IntelliJ IDEA, среда разработки предупредит, что вы пытаетесь изменить неизменяемый объект. Этот код скомпилируется, но завершится с ошибкой во время выполнения с UnsupportedOperationException. По типу коллекции нельзя определить, является ли она изменяемой.
В отличие от Java, в Kotlin вы явно объявляете изменяемые или доступные только для чтения коллекции в зависимости от своих потребностей. Если попытаться изменить коллекцию, доступную только для чтения, код не скомпилируется:
// Kotlin
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five") // This is OK
val immutableNumbers = listOf("one", "two")
//immutableNumbers.add("five") // Compilation error - Unresolved reference: add
Подробнее о неизменяемости см. на странице соглашений по написанию кода Kotlin.
Ковариантность
В Java нельзя передать коллекцию с типом-потомком функции, которая принимает коллекцию с типом-предком. Например, если Rectangle наследуется от Shape, нельзя передать коллекцию элементов типа Rectangle функции, которая принимает коллекцию элементов типа Shape. Чтобы код скомпилировался, используйте тип ? extends Shape, чтобы функция могла принимать коллекции с любыми наследниками Shape:
// Java
class Shape {}
class Rectangle extends Shape {}
public void doSthWithShapes(List<? extends Shape> shapes) {
/* If using just List<Shape>, the code won't compile when calling
this function with the List<Rectangle> as the argument as below */
}
public void main() {
var rectangles = List.of(new Rectangle(), new Rectangle());
doSthWithShapes(rectangles);
}
В Kotlin типы коллекций, доступных только для чтения, ковариантны. Это означает, что если класс Rectangle наследуется от класса Shape, тип List<Rectangle> можно использовать везде, где требуется тип List<Shape>. Иными словами, отношения подтипов между типами коллекций совпадают с отношениями между типами элементов. Отображения ковариантны по типу значения, но не по типу ключа. Изменяемые коллекции не ковариантны — это привело бы к ошибкам во время выполнения.
// Kotlin
open class Shape(val name: String)
class Rectangle(private val rectangleName: String) : Shape(rectangleName)
fun doSthWithShapes(shapes: List<Shape>) {
println("The shapes are: ${shapes.joinToString { it.name }}")
}
fun main() {
val rectangles = listOf(Rectangle("rhombus"), Rectangle("parallelepiped"))
doSthWithShapes(rectangles)
}
Подробнее о типах коллекций читайте здесь.
Диапазоны и прогрессии
В Kotlin можно создавать интервалы с помощью диапазонов. Например, Version(1, 11)..Version(1, 30) включает все версии от 1.11 до 1.30. Проверить, входит ли ваша версия в диапазон, можно с помощью оператора in: Version(0, 9) in versionRange.
В Java нужно вручную проверять, попадает ли Version в заданные границы:
// Java
class Version implements Comparable<Version> {
int major;
int minor;
Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
@Override
public int compareTo(Version o) {
if (this.major != o.major) {
return this.major - o.major;
}
return this.minor - o.minor;
}
}
public void compareVersions() {
var minVersion = new Version(1, 11);
var maxVersion = new Version(1, 31);
System.out.println(
versionIsInRange(new Version(0, 9), minVersion, maxVersion));
System.out.println(
versionIsInRange(new Version(1, 20), minVersion, maxVersion));
}
public Boolean versionIsInRange(Version versionToCheck, Version minVersion,
Version maxVersion) {
return versionToCheck.compareTo(minVersion) >= 0
&& versionToCheck.compareTo(maxVersion) <= 0;
}
В Kotlin диапазон используется как единый объект. Не нужно создавать две переменные и сравнивать с ними Version:
// Kotlin
class Version(val major: Int, val minor: Int): Comparable<Version> {
override fun compareTo(other: Version): Int {
if (this.major != other.major) {
return this.major - other.major
}
return this.minor - other.minor
}
}
fun main() {
val versionRange = Version(1, 11)..Version(1, 30)
println(Version(0, 9) in versionRange)
println(Version(1, 20) in versionRange)
}
Если требуется исключить одну из границ, например проверить, что версия больше или равна (>=) минимальной версии и меньше (<) максимальной, включительные диапазоны не подойдут.
Сравнение по нескольким критериям
В Java для сравнения объектов по нескольким критериям можно использовать функции comparing() и thenComparingX() интерфейса Comparator. Например, для сравнения людей по имени и возрасту:
class Person implements Comparable<Person> {
String name;
int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return this.name + " " + age;
}
}
public void comparePersons() {
var persons = List.of(new Person("Jack", 35), new Person("David", 30),
new Person("Jack", 25));
System.out.println(persons.stream().sorted(Comparator
.comparing(Person::getName)
.thenComparingInt(Person::getAge)).collect(toList()));
}
В Kotlin достаточно перечислить поля, по которым нужно выполнить сравнение:
data class Person(
val name: String,
val age: Int
)
fun main() {
val persons = listOf(Person("Jack", 35), Person("David", 30),
Person("Jack", 25))
println(persons.sortedWith(compareBy(Person::name, Person::age)))
}
Последовательности
В Java последовательность чисел можно сгенерировать так:
// Java
int sum = IntStream.iterate(1, e -> e + 3)
.limit(10).sum();
System.out.println(sum); // Prints 145
В Kotlin используйте последовательности. Многоэтапная обработка последовательностей по возможности выполняется лениво: фактические вычисления происходят только при запросе результата всей цепочки обработки.
fun main() {
//sampleStart
// Kotlin
val sum = generateSequence(1) {
it + 3
}.take(10).sum()
println(sum) // Prints 145
//sampleEnd
}
Последовательности могут уменьшить число шагов, необходимых для выполнения некоторых операций фильтрации. См. пример обработки последовательности, который показывает различие между Iterable и Sequence.
Удаление элементов из списка
В Java функция remove() принимает индекс удаляемого элемента.
При удалении целочисленного элемента передайте функцию Integer.valueOf() в качестве аргумента функции remove():
// Java
public void remove() {
var numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(1);
numbers.remove(1); // This removes by index
System.out.println(numbers); // [1, 3, 1]
numbers.remove(Integer.valueOf(1));
System.out.println(numbers); // [3, 1]
}
В Kotlin есть два способа удаления элемента: по индексу с помощью removeAt() и по значению с помощью remove().
fun main() {
//sampleStart
// Kotlin
val numbers = mutableListOf(1, 2, 3, 1)
numbers.removeAt(0)
println(numbers) // [2, 3, 1]
numbers.remove(1)
println(numbers) // [2, 3]
//sampleEnd
}
Обход отображения
В Java можно обойти отображение с помощью forEach:
// Java
numbers.forEach((k,v) -> System.out.println("Key = " + k + ", Value = " + v));
В Kotlin для обхода отображения используйте цикл for или forEach, аналогичный forEach в Java:
// Kotlin
for ((k, v) in numbers) {
println("Key = $k, Value = $v")
}
// Or
numbers.forEach { (k, v) -> println("Key = $k, Value = $v") }
Получение первого и последнего элементов, возможно, пустой коллекции
В Java безопасно получить первый и последний элементы можно, проверив размер коллекции и используя индексы:
// Java
var list = new ArrayList<>();
//...
if (list.size() > 0) {
System.out.println(list.get(0));
System.out.println(list.get(list.size() - 1));
}
В JDK 21 и более поздних версиях можно также использовать методы getFirst() и getLast(), доступные во всех реализациях SequencedCollection. Сюда входят все реализации List, а также другие коллекции, например LinkedHashSet. Например, для ArrayList:
// Java
var list = new ArrayList<>();
//...
if (!list.isEmpty()) {
System.out.println(list.getFirst());
System.out.println(list.getLast());
}
В Kotlin есть специальные функции firstOrNull() и lastOrNull(). С помощью Elvis operator можно сразу выполнить дальнейшие действия в зависимости от результата функции. Например, firstOrNull():
// Kotlin val emails = listOf<String>() // Might be empty val theOldestEmail = emails.firstOrNull() ?: "" val theFreshestEmail = emails.lastOrNull() ?: ""
Создание множества из списка
В Java для создания Set из List можно использовать функцию Set.copyOf:
// Java
public void listToSet() {
var sourceList = List.of(1, 2, 3, 1);
var copySet = Set.copyOf(sourceList);
System.out.println(copySet);
}
В Kotlin используйте функцию toSet():
fun main() {
//sampleStart
// Kotlin
val sourceList = listOf(1, 2, 3, 1)
val copySet = sourceList.toSet()
println(copySet)
//sampleEnd
}
Группировка элементов
В Java можно группировать элементы с помощью функции Collectors groupingBy():
// Java
public void analyzeLogs() {
var requests = List.of(
new Request("https://kotlinlang.org/docs/home.html", 200),
new Request("https://kotlinlang.org/docs/home.html", 400),
new Request("https://kotlinlang.org/docs/comparison-to-java.html", 200)
);
var urlsAndRequests = requests.stream().collect(
Collectors.groupingBy(Request::getUrl));
System.out.println(urlsAndRequests);
}
В Kotlin используйте функцию groupBy():
data class Request(
val url: String,
val responseCode: Int
)
fun main() {
//sampleStart
// Kotlin
val requests = listOf(
Request("https://kotlinlang.org/docs/home.html", 200),
Request("https://kotlinlang.org/docs/home.html", 400),
Request("https://kotlinlang.org/docs/comparison-to-java.html", 200)
)
println(requests.groupBy(Request::url))
//sampleEnd
}
Фильтрация элементов
В Java для фильтрации элементов коллекции нужно использовать Stream API. В Stream API есть операции intermediate и terminal. filter() — промежуточная операция, которая возвращает поток. Чтобы получить коллекцию в качестве результата, нужно использовать терминальную операцию, например collect(). Например, чтобы оставить только те пары, ключи которых заканчиваются на 1, а значения больше 10:
// Java
public void filterEndsWith() {
var numbers = Map.of("key1", 1, "key2", 2, "key3", 3, "key11", 11);
var filteredNumbers = numbers.entrySet().stream()
.filter(entry -> entry.getKey().endsWith("1") && entry.getValue() > 10)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(filteredNumbers);
}
В Kotlin фильтрация встроена в коллекции, а filter() возвращает коллекцию того же типа, что и исходная. Поэтому достаточно указать filter() и предикат:
fun main() {
//sampleStart
// Kotlin
val numbers = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredNumbers = numbers.filter { (key, value) -> key.endsWith("1") && value > 10 }
println(filteredNumbers)
//sampleEnd
}
Подробнее о фильтрации map.
Фильтрация элементов по типу
В Java, чтобы отфильтровать элементы по типу и выполнить над ними действия, нужно проверить их тип с помощью оператора instanceof, а затем выполнить приведение типа:
// Java
public void objectIsInstance() {
var numbers = new ArrayList<>();
numbers.add(null);
numbers.add(1);
numbers.add("two");
numbers.add(3.0);
numbers.add("four");
System.out.println("All String elements in upper case:");
numbers.stream().filter(it -> it instanceof String)
.forEach( it -> System.out.println(((String) it).toUpperCase()));
}
В Kotlin достаточно вызвать filterIsInstance<NEEDED_TYPE>() для коллекции, а приведение типа выполнит механизм умных приведений типов:
// Kotlin
fun main() {
//sampleStart
// Kotlin
val numbers = listOf(null, 1, "two", 3.0, "four")
println("All String elements in upper case:")
numbers.filterIsInstance<String>().forEach {
println(it.uppercase())
}
//sampleEnd
}
Проверка предикатов
В некоторых задачах нужно проверить, удовлетворяют ли условию все элементы, ни один элемент или хотя бы один элемент. В Java все эти проверки можно выполнить с помощью функций Stream API allMatch(), noneMatch() и anyMatch():
// Java
public void testPredicates() {
var numbers = List.of("one", "two", "three", "four");
System.out.println(numbers.stream().noneMatch(it -> it.endsWith("e"))); // false
System.out.println(numbers.stream().anyMatch(it -> it.endsWith("e"))); // true
System.out.println(numbers.stream().allMatch(it -> it.endsWith("e"))); // false
}
В Kotlin функции-расширения none(), any() и all() доступны для любого объекта Iterable:
fun main() {
//sampleStart
// Kotlin
val numbers = listOf("one", "two", "three", "four")
println(numbers.none { it.endsWith("e") })
println(numbers.any { it.endsWith("e") })
println(numbers.all { it.endsWith("e") })
//sampleEnd
}
Подробнее о проверке предикатов.
Операции преобразования коллекций
Объединение элементов в пары
В Java можно создать пары из элементов, занимающих одинаковые позиции в двух коллекциях, перебирая их одновременно:
// Java
public void zip() {
var colors = List.of("red", "brown");
var animals = List.of("fox", "bear", "wolf");
for (int i = 0; i < Math.min(colors.size(), animals.size()); i++) {
String animal = animals.get(i);
System.out.println("The " + animal.substring(0, 1).toUpperCase()
+ animal.substring(1) + " is " + colors.get(i));
}
}
Если нужно сделать что-то сложнее, чем просто вывести пары элементов, можно использовать Records. В приведённом выше примере запись называлась бы record AnimalDescription(String animal, String color) {}.
В Kotlin для этого используется функция zip():
fun main() {
//sampleStart
// Kotlin
val colors = listOf("red", "brown")
val animals = listOf("fox", "bear", "wolf")
println(colors.zip(animals) { color, animal ->
"The ${animal.replaceFirstChar { it.uppercase() }} is $color" })
//sampleEnd
}
zip() возвращает список объектов Pair.
Связывание элементов
В Java для связывания элементов с характеристиками можно использовать Stream API:
// Java
public void associate() {
var numbers = List.of("one", "two", "three", "four");
var wordAndLength = numbers.stream()
.collect(toMap(number -> number, String::length));
System.out.println(wordAndLength);
}
В Kotlin используется функция associate():
fun main() {
//sampleStart
// Kotlin
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })
//sampleEnd
}
Что дальше?
Посетите раздел Kotlin Koans — выполните упражнения, чтобы изучить синтаксис Kotlin. Каждое упражнение оформлено как модульный тест, который изначально завершается с ошибкой. Ваша задача — сделать так, чтобы он прошёл.
Ознакомьтесь с другими идиомами Kotlin.
Узнайте, как преобразовать существующий код Java в Kotlin с помощью конвертера Java в Kotlin.
Познакомьтесь с коллекциями в Kotlin.
Если у вас есть любимая идиома, поделитесь ею, отправив pull request.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/java-to-kotlin-collections-guide.html