В каких случаях основные структуры данных Kotlin (Карта, Список, Набор) не являются на самом деле неизменными? - PullRequest
0 голосов
/ 20 сентября 2019

Кажется, что структуры данных ядра Kotlin (т. Е. Карта, Список, Набор) действительно являются интерфейсом и не могут быть неизменными .

Если у меня есть:

  fun <K,V> foo(map: Map<K, V>) {
    ...
  }

Может ли map измениться после того, как я его получил - извне?

В каких случаях это возможно?

Ответы [ 3 ]

2 голосов
/ 20 сентября 2019

Давайте запустим эксперимент:

class Foo {

    @Test
    fun foo() {
        val items = mutableListOf("A")
        run(items)
        Thread.sleep(1000)
        items.add("B")
        println("Foo")
        Thread.sleep(2000)
    }

    fun run(items: List<String>) {
        thread(start = true) {
            println("Run ${items.count()}")
            Thread.sleep(2000)
            println("Run ${items.count()}")
        }
    }
}

В этом тестовом примере будет создан изменяемый список из 1 элемента, затем он передаст ссылку на этот список в метод, тип которого предназначен для неизменяемого списка.

Этот метод, называемый run, будет отображать длину списка.

Вне метода run новый список будет добавлен к списку.

Sleep были добавлены, убедитесь, чтодобавление в список происходит после первого оператора run, но перед вторым оператором print.

Давайте рассмотрим вывод:

Run 1
Foo
Run 2

Как мы видим, содержимое списка действительно изменилось, дажехотя run занял неизменный список.

Это потому, что MutableList и List являются просто интерфейсами, и все реализации MutableList также реализуют List.

Когда Kotlin ссылается на изменяемый и неизменяемый объект, он просто указывает, присутствуют ли методы для изменения коллекции, а не может ли быть изменено содержимое.

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

2 голосов
/ 20 сентября 2019

Нет.

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

Но другие реализации не -в частности, MutableMap является подынтерфейсом, поэтому все, что реализует , что является изменяемым Map.Это означает, что код со ссылкой на Map потенциально может видеть изменение данных, даже если он сам не может внести эти изменения.

Аналогично, MutableList является подинтерфейсом List, иMutableSet - это подинтерфейс Set.

. Существуют неизменные реализации этих интерфейсов верхнего уровня (такие как kotlinx.collections.immutable и Guava библиотеки) - и вы могли бы написать свой собственный.Но система языков и типов Kotlin пока не обеспечивает сильной поддержки глубокой неизменности, только для интерфейсов только для чтения с данными, которые могут или не могут быть неизменными.

(Это не значит, что такая поддержка не моглане может быть добавлено в будущем. В нем много интереса , и JetBrains были с учетом этого.)

0 голосов
/ 20 сентября 2019

Как уже указывалось, карта может быть изменена, пока вы используете ее в другом потоке ... однако она уже будет нарушена, если ваш доступ к карте не будет @Synchronized, что будет означать, что вы знали, что она будетизменить, так что эта возможность на самом деле не проблема.Даже если ваш метод принял параметр MutableMap, он был бы неправильным, если он был изменен во время работы вашего метода.

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

Когда ваш метод принимает a Map в качестве параметра, вы указываете, что метод не изменит карту.Цель интерфейса Map только для чтения - позволить вам говорить такие вещи.Вы могли бы сделать (map as? MutableMap)?.put(...), но это было бы неправильно, поскольку вы обещали этого не делать.Вы также можете по-разному завершить процесс или запустить бесконечный цикл, но это также будет неправильно.Просто не делай этого.Язык не обеспечивает защиту от злонамеренных программистов.

Аналогично, если ваш метод возвращает a Map, это означает, что получатель не должен его менять. Обычно в таких случаях вы также обещаете (надеюсь, в комментарии), что возвращаемая карта не изменится.Вы не можете сдержать это обещание, если любой, кто получит карту, сможет изменить ее самостоятельно, и поэтому вы возвращаете Map вместо базового MutableMap

...