Котлин и неизменные коллекции?

Я изучаю Котлина, и, похоже, я могу использовать его в качестве основного языка в следующем году. Тем не менее, я продолжаю сталкиваться с противоречивыми исследованиями, что Котлин делает или не имеет неизменных коллекций, и я пытаюсь выяснить, нужно ли мне использовать Google Guava.

Может кто-нибудь, пожалуйста, дайте мне несколько советов по этому поводу? По умолчанию он использует коллекции Immutable? Какие операторы возвращают изменяемые или неизменные коллекции? Если нет, планируете ли они их реализовать?

Ответ 1

Kotlin List из стандартной библиотеки читается только:

interface List<out E> : Collection<E> (source)

Общий упорядоченный набор элементов. Методы в этом интерфейсе поддерживает только доступ только для чтения к списку; доступ для чтения/записи поддерживается через интерфейс MutableList.

Параметры
E - тип элементов, содержащихся в списке.

Как уже упоминалось, существует также MutableList

interface MutableList<E> : List<E>, MutableCollection<E> (source)

Общий упорядоченный набор элементов, который поддерживает добавление и удаление элементов.

Параметры
E - тип элементов, содержащихся в списке.

В связи с этим Kotlin обеспечивает взаимодействие с readonly через свои интерфейсы, вместо того, чтобы выбрасывать исключения во время выполнения, такие как реализация Java по умолчанию.

Аналогично, существуют MutableCollection, MutableIterable, MutableIterator, MutableListIterator, MutableMap и MutableSet, см. stdlib.

Ответ 2

Это запутанно, но есть три, а не два типа неизменяемости:

  • Mutable - вы должны изменить коллекцию (Kotlin MutableList)
  • Readonly - вы НЕ должны его менять (Kotlin List), но что-то может (сбрасывать на Mutable или меняться с Java)
  • Неизменяемый - никто не может его изменить (коллекции Guavas неизменяемые)

Итак, в случае, если (2) List - это просто интерфейс, который не имеет мутирующих методов, но вы можете изменить экземпляр, если вы применили его к MutableList.

С Guava (case (3)) вы можете быть в безопасности от кого-либо, чтобы изменить коллекцию, даже с литой или из другого потока.

Kotlin выбрал для чтения только для того, чтобы напрямую использовать коллекции Java, поэтому нет никаких накладных расходов или конверсий в использовании коллекций Java.

Ответ 3

Как вы видите в других ответах, Kotlin имеет интерфейсы readonly для изменяемых коллекций, которые позволяют просматривать коллекцию с помощью объектива readonly. Но сборку можно обойти посредством кастинга или манипулирования с Java. Но в совместном коде Kotlin это прекрасно, большинство из них не нуждаются в действительно неизменяемых коллекциях, и если ваша команда избегает приведения в изменяемую форму коллекции, возможно, вам не нужны полностью неизменные коллекции.

Коллекции Котлина допускают как мутации с копированием на изменение, так и ленивые мутации. Поэтому, чтобы ответить на часть ваших вопросов, такие вещи, как filter, map, flatmap, операторы + - все создают копии при использовании против не ленивых коллекций. При использовании в Sequence они изменяют значения как коллекцию по мере ее доступа и продолжают лениться (в результате получается еще Sequence). Хотя при a Sequence, при вызове чего-либо типа toList, toSet, toMap будет выполнена окончательная копия. По соглашению об именах почти все, что начинается с to, составляет копию.

Другими словами, большинство операторов возвращают вам тот же тип, с которого вы начали, и если этот тип "readonly", вы получите копию. Если этот тип ленив, вы лениво применяете это изменение, пока не потребуете сбор в полном объеме.

Некоторые люди хотят их по другим причинам, таким как параллельная обработка. В таких случаях лучше всего взглянуть на действительно высокоэффективные коллекции, созданные только для этих целей. И использовать их в этих случаях не во всех общих случаях.

В мире JVM трудно избежать взаимодействия с библиотеками, требующими стандартных коллекций Java, а преобразование в/из этих коллекций добавляет много боли и накладных расходов для библиотек, которые не поддерживают общие интерфейсы. Kotlin дает хорошее сочетание взаимодействия и отсутствия конверсии с постоянной защитой по контракту.

Итак, если вы не можете избежать ненужных коллекций, Kotlin легко работает с чем-либо из пространства JVM:

Кроме того, команда Котлин работает над Неизменяемыми Коллекциями изначально для Котлина, это усилие можно увидеть здесь:  https://github.com/Kotlin/kotlinx.collections.immutable

Существует множество других фреймворков коллекции для всех различных потребностей и ограничений, Google - ваш друг для их поиска. Нет причин, по которым команда Котлина должна изобретать их для своей стандартной библиотеки. У вас много вариантов, и они специализируются на разных вещах, таких как производительность, использование памяти, не бокс, неизменность и т.д. "Выбор хорош"... поэтому некоторые другие: HPCC, HPCC-RT, FastUtil, Koloboke, Trove и многое другое...

Есть даже такие усилия, как Pure4J, которые с тех пор, как Kotlin поддерживает обработку аннотации сейчас, возможно, может иметь порт для Kotlin для подобных идеалов.

Ответ 4

Kotlin 1.0 не будет иметь неизменных коллекций в стандартной библиотеке. Однако он имеет только доступные для чтения и изменяемые интерфейсы. И ничто не мешает вам использовать сторонние неизменные библиотеки коллекций.

Методы в Kotlin List интерфейс "поддерживает только доступ только для чтения к списку", в то время как методы в MutableList поддержка интерфейса "добавление и удаление элементов". Однако оба они являются только интерфейсами.

Интерфейс Kotlin List обеспечивает доступ только для чтения во время компиляции вместо того, чтобы отложить такие проверки до времени выполнения, например java.util.Collections.unmodifiableList(java.util.List) ( который "возвращает немодифицируемое представление указанного списка... [где] пытается изменить возвращенный список... результат UnsupportedOperationException. Это не обеспечивает неизменность.

Рассмотрим следующий код Котлина:

import com.google.common.collect.ImmutableList
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

fun main(args: Array<String>) {
    val readOnlyList: List<Int> = arrayListOf(1, 2, 3)
    val mutableList: MutableList<Int> = readOnlyList as MutableList<Int>
    val immutableList: ImmutableList<Int> = ImmutableList.copyOf(readOnlyList)

    assertEquals(readOnlyList, mutableList)
    assertEquals(mutableList, immutableList)

    // readOnlyList.add(4) // Kotlin: Unresolved reference: add
    mutableList.add(4)
    assertFailsWith(UnsupportedOperationException::class) { immutableList.add(4) }

    assertEquals(readOnlyList, mutableList)
    assertEquals(mutableList, immutableList)
}

Обратите внимание, что readOnlyList является List, и методы, такие как add, не могут быть разрешены (и не будут компилироваться), MutableList может быть естественным образом изменен, а add на immutableList (из Google Guava) также может быть разрешено во время компиляции, но генерирует исключение во время выполнения.

Все вышеприведенные утверждения проходят, за исключением последнего, что приводит к Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>. Т.е. мы успешно мутировали только для чтения List!

Обратите внимание, что использование listOf(...) вместо arrayListOf(...) возвращает фактически неизменяемый список, поскольку вы не можете использовать его для любого типа изменяемого списка. Однако использование интерфейса List для переменной не препятствует назначению MutableList (MutableList<E> extends List<E>).

Наконец, обратите внимание, что интерфейс в Kotlin (как и в Java) не может обеспечить неизменность, поскольку он "не может хранить состояние" (см. Interfaces). Таким образом, если вы хотите неизменную коллекцию, вам нужно использовать что-то вроде тех, что предоставлены Google Guava.


См. также ImmutableCollectionsExplained · google/guava Wiki · GitHub

Ответ 5

ПРИМЕЧАНИЕ.. Этот ответ здесь, потому что код прост и доступен с открытым исходным кодом, и вы можете использовать эту идею, чтобы сделать ваши коллекции неизменяемыми. Он не предназначен только для рекламы библиотеки.

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

Clutter 1.20.0 выпущен, который добавляет неизменяемые защитники для существующих коллекций, основанные на SO-ответе @miensol, предоставляет легкий делегат вокруг коллекций, который предотвращает любые проспекты модификации, включая литье к изменяемому типу, а затем модификацию. И Klutter делает еще один шаг, защищая подкатегории, такие как iterator, listIterator, entrySet и т.д. Все эти двери закрыты и используют делегацию Kotlin для большинства методов, которые вы не получаете в производительности. Просто позвоните myCollection.asReadonly() (защитите) или myCollection.toImmutable() (копия затем защитите), и результат будет тем же самым интерфейсом, но защищен.

Вот пример из кода, показывающий, насколько просто метод заключается в деле делегирования интерфейса фактическому классу, в то время как переопределение методов мутации и любых возвращенных подкатегорий завернуты на лету.

/**
 * Wraps a List with a lightweight delegating class that prevents casting back to mutable type
 */
open class ReadOnlyList <T>(protected val delegate: List<T>) : List<T> by delegate, ReadOnly, Serializable {
    companion object {
        @JvmField val serialVersionUID = 1L
    }

    override fun iterator(): Iterator<T> {
        return delegate.iterator().asReadOnly()
    }

    override fun listIterator(): ListIterator<T> {
        return delegate.listIterator().asReadOnly()
    }

    override fun listIterator(index: Int): ListIterator<T> {
        return delegate.listIterator(index).asReadOnly()
    }

    override fun subList(fromIndex: Int, toIndex: Int): List<T> {
        return delegate.subList(fromIndex, toIndex).asReadOnly()
    }

    override fun toString(): String {
        return "ReadOnly: ${super.toString()}"
    }

    override fun equals(other: Any?): Boolean {
        return delegate.equals(other)
    }

    override fun hashCode(): Int {
        return delegate.hashCode()
    }
}

Наряду со вспомогательными функциями расширения, чтобы упростить доступ:

/**
 * Wraps the List with a lightweight delegating class that prevents casting back to mutable type,
 * specializing for the case of the RandomAccess marker interface being retained if it was there originally
 */
fun <T> List<T>.asReadOnly(): List<T> {
    return this.whenNotAlreadyReadOnly {
        when (it) {
            is RandomAccess -> ReadOnlyRandomAccessList(it)
            else -> ReadOnlyList(it)
        }
    }
}

/**
 * Copies the List and then wraps with a lightweight delegating class that prevents casting back to mutable type,
 * specializing for the case of the RandomAccess marker interface being retained if it was there originally
 */
@Suppress("UNCHECKED_CAST")
fun <T> List<T>.toImmutable(): List<T> {
    val copy = when (this) {
        is RandomAccess -> ArrayList<T>(this)
        else -> this.toList()
    }
    return when (copy) {
        is RandomAccess ->  ReadOnlyRandomAccessList(copy)
        else -> ReadOnlyList(copy)
    }
}

Вы можете увидеть идею и экстраполировать, чтобы создать недостающие классы из этого кода, который повторяет шаблоны для других ссылочных типов. Или просмотрите полный код здесь:

https://github.com/kohesive/klutter/blob/master/core-jdk6/src/main/kotlin/uy/klutter/core/common/Immutable.kt

И с тестами, показывающими некоторые из трюков, которые ранее допускали модификации, но теперь нет, вместе с заблокированными нажатиями и вызовами с использованием этих оберток.

https://github.com/kohesive/klutter/blob/master/core-jdk6/src/test/kotlin/uy/klutter/core/collections/TestImmutable.kt