Spec-Zone.ru › Kotlin 1.6

Коллекции в 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

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

Описание

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

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

Если вам нужно сделать что-то более сложное, чем просто вывести пары элементов, вы можете использовать записи. В примере выше запись будет 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 можно использовать 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.

Последнее изменение: 07 апреля 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