Как может множество Котлина быть ковариантным, когда содержит () принимает E? - PullRequest
3 голосов
/ 22 мая 2019

Я искал со-и противоречивость в библиотеках коллекций нескольких языков программирования и наткнулся на интерфейс Set Kotlin.

Он задокументирован как

interface Set<out E> : Collection<E>

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

И Set<String> становится подтипом Set<Any>.

Тем не менее, у него есть эти два метода:

abstract fun contains(element: E): Boolean
abstract fun containsAll(elements: Collection<E>): Boolean

Поэтому, когда я создаю класс, реализующий Set<String>, я должен реализовать (помимо других) contains(String).Но позже кто-то может использовать мой класс в качестве Set<Any> и позвонить set.contains(5).

Я действительно попробовал это:

class StringSet : Set<String> {
    override val size = 2
    override fun contains(element: String): Boolean {
        println("--- StringSet.contains($element)")
        return element == "Hallo" || element == "World"
    }

    override fun containsAll(elements: Collection<String>) : Boolean =
        elements.all({it -> contains(it)})
    override fun isEmpty() = false
    override fun iterator() = listOf("Hallo", "World").iterator()

}

fun main() {
    val sset : Set<String> = StringSet()
    println(sset.contains("Hallo"))
    println(sset.contains("xxx"))
    //// compiler error:
    // println(set.contains(5))

    val aset : Set<Any> = sset
    println(aset.contains("Hallo"))
    println(aset.contains("xxx"))
    // this compiles (and returns false), but the method is not actually called
    println(aset.contains(5)) 
}

( Запустить онлайн )

Таким образом, получается, что Set<String> не является "реальным" подтипом Set<Any>, поскольку set.contains(5) работает со вторым, но не с первым.

На самом деле вызов метода содержит дажеработает во время выполнения - просто моя реализация никогда не будет вызвана, и он просто печатает false.

Глядя на исходный код интерфейса, оказывается, что два метода фактически объявлены как

abstract fun contains(element: @UnsafeVariance E): Boolean
abstract fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean

Что здесь происходит?Есть ли какая-то особая магия компилятора для Set?Почему это нигде не задокументировано?

1 Ответ

5 голосов
/ 22 мая 2019

Ковариация сайта декларации в виде модификатора out упускает полезный вариант использования, чтобы убедиться, что экземпляр, переданный в качестве аргумента, обычно целесообразно передавать здесь.Хорошими примерами являются функции contains.

В конкретном случае Set.contains аннотация @UnsafeVariance используется, чтобы гарантировать, что функция принимает экземпляр E, передавая element это не E в contains не имеет смысла - вся правильная реализация Set всегда будет возвращать false.Реализации Set не должны хранить element, переданный в contains, и поэтому никогда не должны возвращать его из какой-либо другой функции с типом возврата E.Таким образом, правильно реализованный Set не будет нарушать ограничения на дисперсию во время выполнения.

Аннотация @UnsafeVariance фактически подавляет конфликты дисперсии компилятора, подобно использованию out -проектированного параметра типа в in-position.

Его мотивация лучше всего описана в этом сообщении в блоге :

@UnsafeVariance аннотация

Иногда нам нужно подавить объявлениепроверка отклонений в наших классах.Например, чтобы сделать Set.contains типобезопасным при сохранении ко-варианта наборов только для чтения, мы должны были сделать это:

interface Set<out E> : Collection<E> {
     fun contains(element: @UnsafeVariance E): Boolean
}

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

Итак, для этой цели мы ввели аннотацию @UnsafeVariance для типов.Это было сделано намеренно долго и заслуживает предостережения против злоупотребления им.

В остальной части поста в блоге также явно упоминается, что подпись contains с использованием @UnsafeVariance повышает безопасность типов.

Альтернативой введению @UnsafeVariance было сохранение contains принятия Any, но в этом параметре отсутствует проверка типа на вызовах contains, которые обнаруживали бы ошибочные вызовы с element s.которые не могут присутствовать в наборе из-за отсутствия экземпляров E.

...