Как справиться с этим преобразованием общего списка из Java в Kotlin?

#java #android #list #generics #kotlin

#java #Android #Список #общие сведения #kotlin

Вопрос:

В процессе перезаписи кода Java в Kotlin мне нужно определить тип объекта списка. Дело в том, что он неизвестен и может содержать объекты разного типа. Ситуация:

В моем классе Java я определил список с TopicNode объектами: private List<TopicNode> nodes . Эти объекты могут содержать сообщения типа T extends Message . Сообщения могут быть типа Boolean , Int32 String и т.д. Узел выглядит как:

TopicNode.java

 class TopicNode<T extends Message> extends AbstractNodeMain {
    // Implementation
}
  

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

TopicControllerImpl.java

 public class TopicControllerImpl implements TopicController {

    private List<TopicNode> nodes;

    @Override
    public void subscribe(String topic, Class type, Messenger subscriber) {
        TopicNode node = findOrCreateNode(topic, type);
        node.addListener(subscriber);
    }

    private <T extends Message> TopicNode<T> findNode(String topic) {
        for (TopicNode node : nodes) {
            if (node.getTopic().equals(topic)) {
                return (TopicNode<T>) node;
            }
        }
        return null;
    }

    private <T extends Message> TopicNode findOrCreateNode(String topic, Class<T> type) {
        TopicNode<T> node = findNode(topic);
        if (node != null) {
            return node;
        }
        // If node doesn't exist yet, create it, run it and add it to the collection
        node = new TopicNode<>(topic, type);
        executor.execute(node, configuration);
        nodes.add(node);
        return node;
    }
}
  

Когда я пытаюсь определить подобный список в Kotlin ( private val nodes: ArrayList<TopicNode> = ArrayList() ), компилятор говорит: One type argument expected for class TopicNode<T : Message> .

Чтобы решить эту проблему, вы можете определить класс и список следующим образом в Kotlin:

TopicControllerImpl.kt

 class TopicControllerImpl<T : Message>(/** args */) : TopicController<T> {
    private val nodes: ArrayList<TopicNode<T>> = ArrayList()

    override fun subscribe(topic: String, type: Class<T>, subscriber: Messenger) {
        val node = findOrCreateNode(topic, type)
        node.addListener(subscriber)
    }

    private fun findNode(topic: String): TopicNode<T>? {
        return nodes.firstOrNull { it.topic == topic }
    }

    private fun findOrCreateNode(topic: String, type: Class<T>): TopicNode<T> 
    {
        var node = findNode(topic)
        if (node != null) {
            return node
        }
        // If node doesn't exist yet, create it, run it and add it to the collection
        node = TopicNode(topic, type)
        executor.execute(node, configuration)
        nodes.add(node)

        return node
    }
}
  

Проблема с этим заключается в том, что вам нужно определить тип, T который используется для списка в TopicControllerImpl , пока это неизвестно, и это не требуется в Java. Неизвестно, каким будет тип Message , и он также может варьироваться.

Поскольку это так, как я могу справиться с этой ситуацией в Kotlin? Возможно ли это вообще?

Заранее большое спасибо!

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

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

2. @Klyner, я пытался преобразовать ваш код в kotlin. Это соответствует вашим требованиям?

3. @theapache64 Проблема, с которой сталкивается это решение, заключается в том, что когда вы хотите определить, TopicControllerImpl вам нужно указать T . T является переменной в списке тематических узлов в Java, но Kotlin хочет, чтобы мы ее указали.

4. @Klyner вы решили проблему? Если возможно, я хотел бы знать, что вы сделали.

Ответ №1:

Вот разница между общим и шаблонным. Например

 private val nodes: ArrayList<TopicNode<T>> = ArrayList()
  

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

 private val nodes: ArrayList<TopicNode<*>> = ArrayList()
  

означает, что у меня будет массив этого общего класса со смешанным типом.

Если вы хотите использовать первый, измените свое определение класса, но если вы хотите второе, измените свое объявление массива.

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

1. Как и почему я должен изменить свое определение массива?

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

3. Как вы видите в моем примере кода TopicControllerImpl.kt , я уже использовал общее решение типа raw. Но это приводит к проблеме, заключающейся в том, что TopicControllerImpl необходимо определить с помощью указанного объекта T . В Java этого можно было бы избежать, но в Kotlin, похоже, этого избежать невозможно.

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

5. @Klyner кстати, я смешал определение массива и класса, поэтому я обновил свой ответ, извините за путаницу. Если вы хотите тип raw, измените свое определение класса, как вы это сделали. Если вы хотите смешанный тип, измените свое определение массива.

Ответ №2:

You should never use raw types in Java in the first place, see wildcards or their Kotlin equivalent - @Moira

Я решил эту проблему, избегая использования необработанного типа T . Итак, я удалил <T : Message> в определении класса; это означает, что везде, где T используется, объект, заполняющий это, должен расширяться от Message .

Допустим, у нас есть объект, Monkey который является подклассом абстрактного класса Animal .

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

 List<Monkey> monkeys = getMonkeys()
List<Animal> animals = monkeys      // Will compile with Kotlin; will not compile for Java
  

Эта возможность сделать это вызвана так называемым covariance .

Дополнительная информация о дженериках и различиях:https://proandroiddev.com/understanding-generics-and-variance-in-kotlin-714c14564c47

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

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