#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 игнорируют отношение типа к подтипу между его компонентами. В случае, когда список не может быть назначен списку или наоборот, это называется инвариантностью. Здесь нет отношения между подтипом и супертипом.