Расширить класс данных в Котлине
Классы данных, похоже, заменяют старомодные 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()
Ответы
Ответ 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()
Это сработало.