Как исправить эту непроверенную ошибку приведения при использовании дженериков?

#java #list #generics #kotlin

#java #Список #дженерики #kotlin

Вопрос:

В моем классе у меня есть список, содержащий TopicNodes. Узлы этого списка должны исходить из класса Message . В методе findNode в списке узлов выполняется поиск узла с определенной темой, и если он соответствует, он возвращается. Компилятор Java жалуется на то, что TopicNode преобразуется в TopicNode типа T , поскольку возможно, что он не имеет типа T . Каков наилучший способ решить эту проблему?

 private val nodes: MutableList<TopicNode<*>>

init {
    this.nodes = ArrayList()
}

private fun <T : Message> findNode(topic: String): TopicNode<T>? {
    for (node in nodes) {
        if (node.topic == topic) {
            return node as TopicNode<T> // Unchecked cast: TopicNode<*> to TopicNode<T>
        }
    }
    return null
}
 

Комментарии:

1. Узлы этого списка должны исходить из класса Message . — почему <*> тогда?

2. Может быть, тогда вопрос должен быть: how to define that the objects in a list should extend from another class? я не знаю, как это сделать @TimCastelijns

Ответ №1:

Я подозреваю, что это должно параметризовать класс, а не метод.

Код в вопросе должен исходить из класса, который не показан. Каждый экземпляр этого класса содержит список узлов. И, судя по методу, каждый класс содержит узлы определенного типа сообщений. Но метод а) требует, чтобы вызывающий объект заранее знал, какой тип, и б) позволяет вызывать его в одном экземпляре для разных типов, что не имело бы никакого смысла!

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

 class MyClass<T : Message> {
    private val nodes: MutableList<TopicNode<T>>

    init {
        this.nodes = ArrayList()
    }

    private fun findNode(topic: String): TopicNode<T>? {
        for (node in nodes) {
            if (node.topic == topic) {
                return node // No need to cast
            }
        }
        return null
    }
}
 

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

Фактически, класс можно было бы упростить до:

 class MyClass<T : Message> {
    private val nodes: MutableList<TopicNode<T>> = ArrayList()

    private fun findNode(topic: String)
        = nodes.find{ it.topic == topic }
}
 

Ответ №2:

Публикуем это там, на всякий случай, если это соответствует варианту использования OP, и им действительно не нужны «расширенные дженерики» в их списке. Иногда вы не видите леса за деревьями

 private val nodes: MutableList<TopicNode<Message>>

private fun findNode(topic: String) = nodes.firstOrNull{ it.topic == topic}
 

Если TopicNode является ковариантным, то, например, a TopicNode<MyMessage> может быть добавлен в nodes противном случае компилятор выдаст ошибку.

Как мне узнать, является ли TopicNode ковариантным? Это ковариантно, если TopicNode объявлен как:

 class TopicNode<Out T> {
 

Он поставляется с набором ограничений для этого класса. Для получения дополнительной информации по этой теме https://kotlinlang.org/docs/reference/generics.html#variance

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

Комментарии:

1. Выглядит как отличное решение. Но стоит ли указывать Message в качестве типа для TopicNode ? Может ли, например, TopicNode MyMessage , который расширяется, Message быть добавлен в список?

Ответ №3:

Я думаю, что сам нашел решение. Вы можете использовать .filterIsInstance<T>() для списка фильтрацию элементов типа T в списке.

Это приводит к следующему решению:

  private fun <T : Message> findNode(topic: String): TopicNode<T>? {
    val messageNodes: List<TopicNode<T>> = nodes.filterIsInstance<TopicNode<T>>()
    for (node in messageNodes) {
        if (node.topic == topic) {
            return node
        }
    }
    return null
}
 

Он проверяет, есть ли элементы в списке типа TopicNode<T> where T extends from Message .

Комментарии:

1. Ваш код может быть записан как: return nodes.filterIsInstance<TopicNode<T>>().firstOrNull {it.topic == topic} в kotlin

2. @Klyner вы ищете один конкретный элемент, но предварительно фильтруете весь список. это было бы короче: private fun <T: Message> findNode(topic: String) = nodes.firstOrNull { (it as? TopicNode<T>)?.topic == topic } и потребовало бы только одной итерации. но тогда становится очевидным, что исходная проблема все еще существует, если nodes имеет тип MutableList<TopicNode<*>>