Подтвердить что ты не робот

Расширить класс данных в Котлине

Классы данных, похоже, заменяют старомодные POJO в Java. Весьма вероятно, что эти классы позволят наследовать, но я не вижу удобного способа расширения класса данных. Мне нужно что-то вроде этого:

open data class Resource (var id: Long = 0, var location: String = "")
data class Book (var isbn: String) : Resource()

Сбой кода из-за столкновения методов component1(). Оставляя data аннотацию только в одном из классов, тоже не выполняет работу.

Возможно, существует еще одна идиома для расширения классов данных?

UPD: я мог бы аннотировать только дочерний дочерний класс, но data аннотация обрабатывает только свойства, объявленные в конструкторе. То есть, я должен был бы объявить все родительские свойства open и переопределить их, что является уродливым:

open class Resource (open var id: Long = 0, open var location: String = "")
data class Book (
    override var id: Long = 0,
    override var location: String = "",
    var isbn: String
) : Resource()
4b9b3361

Ответ 1

По правде говоря: классы данных не слишком хорошо сочетаются с наследованием. Мы рассматриваем возможность запрещения или строгого ограничения наследования классов данных. Например, он знал, что нет способа правильно реализовать equals() в иерархии на не-абстрактных классах.

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

Ответ 2

Объявлять свойства в суперклассе вне конструктора как абстрактные и переопределять их в подклассе.

abstract class Resource {
    abstract var id: Long
    abstract var location: String
}

data class Book (
    override var id: Long = 0,
    override var location: String = "",
    var isbn: String
) : Resource()

Ответ 3

Вышеупомянутое решение с использованием абстрактного класса фактически генерирует соответствующий класс, и пусть класс данных расширяется от него.

Если вы не предпочитаете абстрактный класс, как насчет использования интерфейса?

Интерфейс в Kotlin может иметь свойства, как показано в этой статье.

interface History {
    val date: LocalDateTime
    val name: String
    val value: Int
}

data class FixedHistory(override val date: LocalDateTime,
                        override val name: String,
                        override val value: Int,
                        val fixedEvent: String) : History

Мне было любопытно, как Котлин скомпилирует это. Здесь эквивалентный Java-код (сгенерированный с использованием функции Intellij [Kotlin bytecode]):

public interface History {
   @NotNull
   LocalDateTime getDate();

   @NotNull
   String getName();

   int getValue();
}

public final class FixedHistory implements History {
   @NotNull
   private final LocalDateTime date;
   @NotNull
   private final String name;
   private int value;
   @NotNull
   private final String fixedEvent;

   // Boring getters/setters as usual..
   // copy(), toString(), equals(), hashCode(), ...
}

Как видите, он работает точно так же, как обычный класс данных!

Ответ 4

@Желько Трогрлич ответ правильный. Но мы должны повторить те же поля, что и в абстрактном классе.

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

abstract class AbstractClass {
    abstract val code: Int
    abstract val url: String?
    abstract val errors: Errors?

    abstract class Errors {
        abstract val messages: List<String>?
    }
}



data class History(
    val data: String?,

    override val code: Int,
    override val url: String?,
    // Do not extend from AbstractClass.Errors here, but Kotlin allows it.
    override val errors: Errors?
) : AbstractClass() {

    // Extend a data class here, then you can use it for 'errors' field.
    data class Errors(
        override val messages: List<String>?
    ) : AbstractClass.Errors()
}

Ответ 5

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

Ответ 6

Черты Kotlin могут помочь.

interface IBase {
    val prop:String
}

interface IDerived : IBase {
    val derived_prop:String
}

классы данных

data class Base(override val prop:String) : IBase

data class Derived(override val derived_prop:String,
                   private val base:IBase) :  IDerived, IBase by base

пример использования

val b = Base("base")
val d = Derived("derived", b)

print(d.prop) //prints "base", accessing base class property
print(d.derived_prop) //prints "derived"

Этот подход также может быть решением проблемы наследования с @Parcelize

@Parcelize 
data class Base(override val prop:Any) : IBase, Parcelable

@Parcelize // works fine
data class Derived(override val derived_prop:Any,
                   private val base:IBase) : IBase by base, IDerived, Parcelable

Ответ 7

Вы можете наследовать класс данных от не-класса данных.

Базовый класс

open class BaseEntity (

@ColumnInfo(name = "name") var name: String? = null,
@ColumnInfo(name = "description") var description: String? = null,
// ...
)

детский класс

@Entity(tableName = "items", indices = [Index(value = ["item_id"])])
data class CustomEntity(

    @PrimaryKey
    @ColumnInfo(name = "id") var id: Long? = null,
    @ColumnInfo(name = "item_id") var itemId: Long = 0,
    @ColumnInfo(name = "item_color") var color: Int? = null

) : BaseEntity()

Это сработало.