Общая встроенная функция - PullRequest
       1

Общая встроенная функция

0 голосов
/ 06 сентября 2018

Допустим, у меня есть объект, который помогает мне десериализовать другие объекты из хранилища:

val books: MutableList<Book> = deserializer.getBookList()
val persons: MutableList<Person> = deserializer.getPersonList()

Методы getBookList и getPersonList являются функциями расширения, которые я написал. Их логика почти одинакова, поэтому я подумал, что могу объединить их в один метод. Моя проблема заключается в типе возвращаемого значения. Методы выглядят так:

fun DataInput.getBookList(): MutableList<Book> {
    val list = mutableListOf<Book>()
    val size = this.readInt()

    for(i in 0 .. size) {
        val item = Book()
        item.readExternal(this)
        list.add(item)
    }

    return list
}

Есть ли какая-то магия Котлина (возможно, со встроенными функциями), которую я могу использовать для обнаружения типа List и генерирования этих методов? Я думаю, что проблема будет val item = T(), которая не будет работать для универсальных типов, верно? Или это возможно с помощью встроенных функций?

Ответы [ 2 ]

0 голосов
/ 06 сентября 2018

Примечание:

Как упомянуто в комментарии marstran, для этого требуется, чтобы класс работал с конструктором с нулевым аргументом, или он выдаст исключение в runtime . Компилятор не предупредит вас, если конструктор не существует, поэтому, если вы выберете этот путь, убедитесь, что вы действительно передаете класс с конструктором с нулевым аргументом.


Вы не можете инициализировать универсальные типы в Kotlin или Java. По крайней мере, не "традиционным" способом. Вы не можете сделать это:

val item = T()

В Java вы должны передать Class<T> и получить конструктор. Очень простой пример этого:

public <T> void x(Class<T> cls){
    cls.getConstructor().newInstance(); // Obviously you'd do something with the return value, but this is just a dummy example
}

Вы можете сделать то же самое в Kotlin, но у Kotlin есть ключевое слово reified, которое делает его немного легче. Это требует встроенной функции, что означает, что вы изменили бы свою функцию на:

inline fun <reified T> DataInput.getBookList(): MutableList<T> { // Notice the `<reified T>`
    val list = mutableListOf<T>() // Use T here
    val size = this.readInt()

    for(i in 0 .. size) {
        // This is where the initialization happens; you get the constructor, and create a new instance.
        // Also works with arguments, if you have any, but you used an empty one so I assume yours is empty
        val item = T::class.java.getConstructor().newInstance()!!
        item.readExternal(this) // However, this is tricky. See my notes below this code block
        list.add(item)
    }

    return list
}

Однако, readExternal отсутствует в Any, что создаст проблемы. Единственное исключение - если у вас есть функция расширения для Any или универсального типа с этим именем и вводом.

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

class Book(){
    fun readExternal(input: DataInput) { /*Foo bar */}
}

class Person(){
    fun readExternal(input: DataInput) { /*Foo bar */}
}

Не будет работать. Нет общего родителя, кроме Any, а Any не имеет readExternal. Метод определяется в каждом из них вручную.

Вы можете создать общего родителя как интерфейс или абстрактный класс (при условии, что его еще нет) и использовать <reified T : TheSharedParent>, и у вас будет к нему доступ.

Конечно, вы можете использовать рефлексию, но она немного сложнее и добавляет некоторые исключения, которые вам нужно обработать. Я не рекомендую делать это; Я бы лично использовал суперкласс.

inline fun <reified T> DataInput.getBookList(): MutableList<T> {
    val list = mutableListOf<T>()
    val size = this.readInt()
    val method = try {
        T::class.java.getMethod("readExternal", DataInput::class.java)
    }catch(e: NoSuchMethodException){
        throw RuntimeException()
    }catch(e: SecurityException){
        throw RuntimeException()// This could be done better; but error handling is up to you, so I'm just making a basic example
        // The catch clauses are pretty self-explanatory; if something happens when trying to get the method itself,
        // These two catch them
    }
    for(i in 0 .. size) {
        val item: T = T::class.java.getConstructor().newInstance()!!
        method.invoke(item, this)
        list.add(item)
    }

    return list
}
0 голосов
/ 06 сентября 2018

Вы не можете вызвать конструктор универсального типа, потому что компилятор не может гарантировать, что у него есть конструктор (тип может быть из интерфейса).Чтобы обойти это, вы можете передать функцию-создатель в качестве параметра вашей функции.Вот так:

fun <T> DataInput.getList(createT: () -> T): MutableList<T> {
    val list = mutableListOf<T>()
    val size = this.readInt()

    for(i in 0 .. size) {
        val item = createT()
        /* Unless readExternal is an extension on Any, this function 
         * either needs to be passed as a parameter as well,
         * or you need add an upper bound to your type parameter
         * with <T : SomeInterfaceWithReadExternal>
         */
        item.readExternal(this)
        list.add(item)
    }

    return list
}

Теперь вы можете вызывать функцию следующим образом:

val books: MutableList<Book> = deserializer.getList(::Book)
val persons: MutableList<Person> = deserializer.getList(::Person)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...