Spec-Zone.ru › Kotlin 1.7

Коллекции в Java и Kotlin

Коллекции представляют собой группы переменного числа элементов (возможно, нуля), которые важны для решаемой задачи и с которыми часто проводятся операции. Это руководство объясняет и сравнивает концепции и операции с коллекциями в Java и Kotlin. Оно поможет вам перейти с Java на Kotlin и писать код на языке Kotlin аутентичным способом.

Первая часть этого руководства содержит краткий глоссарий операций над одними и теми же коллекциями в Java и Kotlin. Она разделена на одинаковые операции и операции, существующие только в Kotlin. Вторая часть руководства, начиная с Изменяемости, объясняет некоторые из различий, рассматривая конкретные случаи.

Для ознакомления с коллекциями см. Обзор коллекций или посмотрите это видео Себастьяна Айгнера, разработчика Kotlin.

Все примеры ниже используют только API стандартных библиотек Java и Kotlin.

Операции, одинаковые в Java и Kotlin

В Kotlin есть много операций над коллекциями, которые выглядят точно так же, как их аналоги в Java.

Операции над списками, множествами, очередями и деками

Описание

Общие операции

Больше альтернатив Kotlin

Добавить элемент или элементы

add(), addAll()

Используйте оператор plusAssign(+=): collection += element, collection += anotherCollection.

Проверить, содержит ли коллекция элемент или элементы

contains(), containsAll()

Используйте ключевое слово in для вызова contains() в операторной форме: element in collection.

Проверить, пуста ли коллекция

isEmpty()

Используйте isNotEmpty() для проверки, не пуста ли коллекция.

Удалить при определенном условии

removeIf()

Оставить только выбранные элементы

retainAll()

Удалить все элементы из коллекции

clear()

Получить поток из коллекции

stream()

Kotlin имеет свой собственный способ обработки потоков: последовательности и методы, такие как map() и filter().

Получить итератор из коллекции

iterator()

Операции над картами

Описание

Общие операции

Больше альтернатив Kotlin

Добавить элемент или элементы

put(), putAll(), putIfAbsent()

В Kotlin присваивание map[key] = value работает так же, как put(key, value). Также можно использовать оператор plusAssign(+=): map += Pair(key, value) или map += anotherMap.

Заменить элемент или элементы

put(), replace(), replaceAll()

Используйте оператор индексирования map[key] = value вместо put() и replace().

Получить элемент

get()

Используйте оператор индексирования для получения элемента: map[index].

Проверить, содержит ли карта элемент или элементы

containsKey(), containsValue()

Используйте ключевое слово in для вызова contains() в операторной форме: element in map.

Проверить, пуста ли карта

isEmpty()

Используйте isNotEmpty() для проверки, не пуста ли карта.

Удалить элемент

remove(key), remove(key, value)

Используйте оператор minusAssign(-=): map -= key.

Удалить все элементы из карты

clear()

Получить поток из карты

stream() по записям, ключам или значениям

Операции, существующие только для списков

Описание

Общие операции

Больше альтернатив Kotlin

Получить индекс элемента

indexOf()

Получить последний индекс элемента

lastIndexOf()

Получить элемент

get()

Используйте оператор индексирования для получения элемента: list[index].

Взять подсписок

subList()

Заменить элемент или элементы

set(), replaceAll()

Используйте оператор индексирования вместо set(): list[index] = value.

Операции, которые немного отличаются

Операции над любым типом коллекции

Описание

Java

Kotlin

Получение размера коллекции

size()

count(), size

Получение плоского доступа к элементам вложенной коллекции

collectionOfCollections.forEach(flatCollection::addAll) или collectionOfCollections.stream().flatMap().collect()

flatten() или flatMap()

Применение заданной функции ко всем элементам

stream().map().collect()

map()

Применение предоставленной операции к элементам коллекции последовательно и возврат накопленного результата

stream().reduce()

reduce(), fold()

Группировка элементов по классификатору и подсчет их количества

stream().collect(Collectors.groupingBy(classifier, counting()))

eachCount()

Фильтрация по условию

stream().filter().collect()

filter()

Проверка того, удовлетворяют ли элементы коллекции условию

stream().noneMatch(), stream().anyMatch(), stream().allMatch()

none(), any(), all()

Сортировка элементов

stream().sorted().collect()

sorted()

Получение первых N элементов

stream().limit(N).collect()

take(N)

Получение элементов с помощью предиката

stream().takeWhile().collect()

takeWhile()

Пропуск первых N элементов

stream().skip(N).collect()

drop(N)

Пропуск элементов с помощью предиката

stream().dropWhile().collect()

dropWhile()

Создание словарей из элементов коллекции и определенных значений, связанных с ними

stream().collect(toMap(keyMapper, valueMapper))

associate()

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

Операции над списками

Описание

Java

Kotlin

Сортировка списка в естественном порядке

sort(null)

sort()

Сортировка списка в порядке убывания

sort(comparator)

sortDescending()

Удаление элемента из списка

remove(index), remove(element)

removeAt(index), remove(element) или collection -= element

Заполнение всех элементов списка определенным значением

Collections.fill()

fill()

Получение уникальных элементов из списка

stream().distinct().toList()

distinct()

Операции, которых нет в стандартной библиотеке Java

  • zip(), unzip() – преобразование коллекции.

  • aggregate() – группировка по условию.

  • takeLast(), takeLastWhile(), dropLast(), dropLastWhile() – взятие или пропуск элементов по предикату.

  • slice(), chunked(), windowed() – извлечение частей коллекции.

  • Операторы «плюс» (+) и «минус» (-) – добавление или удаление элементов.

Если вы хотите углубиться в 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, IDE предупредит вас о попытке модифицировать неизменяемый объект. Этот код будет компилироваться, но аварийно завершится в процессе выполнения с 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)
}

Как только вам нужно исключить одну из границ, например, проверить, больше или равна ли версия минимальной версии (>=) и меньше максимальной версии (<), эти включенные диапазоны не помогут.

END_OF_DOCUMENT_MARKER

Сравнение по нескольким критериям

В 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));
}

Также можно использовать функции getFirst() и getLast() для Deque и его наследников:

// Java
var deque = new ArrayDeque<>();
//...
if (deque.size() > 0) {
    System.out.println(deque.getFirst());
    System.out.println(deque.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():

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
}

Дополнительную информацию о фильтрации карт см. здесь.

Фильтрация элементов по типу

В 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>() на вашей коллекции, и приведение типов выполняется интеллектуальными приведениями Smart casts:

// 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 для каждого объекта Iterable доступны расширяющие функции none(), any(), и all():

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.

Если у коллекций разная длина, результат zip() имеет длину меньшей коллекции. Элементы большей коллекции, начиная с последнего, не включаются в результат.

Сопоставление элементов

В 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.

Если у вас есть любимая идиома, поделитесь ею, отправив запрос на добавление.

Последнее изменение: 12 августа 2022
Строки в Java и Kotlin Начало работы с Kotlin/JS для React

© 2010–2022 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API