Карта с типом или классом - PullRequest
2 голосов
/ 17 марта 2019

В Kotlin я могу использовать filterIsInstance для получения подгруппы, зависящей от типа (и безопасной для типов):

val misc: List<Any> = listOf(42, 3.14, true, "foo", "bar")
val strings: List<String> = misc.filterIsInstance<String>()
println(strings) // => [foo, bar]

Но у меня есть большая коллекция объектов, и я хотел бы предварительно отсортировать ее в карту по конкретному типу. Можно ли вообще определить такую ​​карту в системе типов Котлина?

val miscByType: Map<KType, Collection<???>>

или

val miscByClass: Map<KClass, Collection<???>>

Должен ли я использовать пользовательскую реализацию с небезопасным (но логически обоснованным) приведением?

Ниже приведена такая реализация. Это работает, но мне интересно, есть ли менее хакерский способ сделать это:

import kotlin.reflect.KClass

class InstanceMap {
    // INVARIANT: map from KClass to a set of objects of *that concrete class*
    private val map: MutableMap<KClass<*>, MutableSet<Any>> = mutableMapOf()

    // this is the only public mutator, it guarantees the invariant
    fun add(item: Any): Boolean =
        map.getOrPut(item::class) { mutableSetOf() }.add(item)

    // public non-inline accessor, only needed by the inline accessor
    fun get(cls: KClass<*>): Set<*>? = map[cls]

    // inline accessor that performs an unsafe, but sound, cast
    @Suppress("UNCHECKED_CAST")
    inline fun <reified T> get(): Set<T> = get(T::class) as Set<T>? ?: setOf()
}

fun instanceMapOf(vararg items: Any): InstanceMap = InstanceMap().apply {
    items.forEach { add(it) }
}

val misc = instanceMapOf(42, 3.14, true, "foo", "bar")
val strings = misc.get<String>()
println(strings) // => [foo, bar]

1 Ответ

2 голосов
/ 17 марта 2019

Ваш код выглядит нормально.Единственная проблема связана с непроверенным предупреждением.На уровне байт-кода JVM приведение ничего не делает из-за способа реализации обобщений в Java и Kotlin.Он также известен как стирание типа .
https://en.wikipedia.org/wiki/Type_erasure

Стирание типа добавляет еще одну проблему в ваш код - он не сообщает аргументы универсального типа.Так, например, List<Int> имеет тот же класс, что и List<String> или List<Map<String, Object>>

Ожидаете ли вы, что ваш код найдет суперклассы или интерфейсы на карте?Например, если у меня есть

interface A
interface B
class C : A, B
val m = InstanceMap()
m.add(C())

m.get(C::class)
m.get(A::class)
m.get(B::class)

Ожидаете ли вы, что эти 3 вызова возвращают одно и то же значение?

Стандартный обходной путь JVM для него заключается в явной передаче параметра Class<T> и использовании Class#cast метод для применения вместо.Это изменение сделает код более безопасным.

В Kotlin есть средство для удаления типа.Вы можете добавить встроенную функцию reified, чтобы компилятор Kotlin использовал точный тип в теле встроенной обобщенной функции
https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters

inline fun <reified T> InstanceMap.get() = get(T::class)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...