Data classes seem to be the replacement to the old-fashioned POJOs in Java. It is quite expectable that these classes would allow for inheritance, but I can see no convenien
While implementing equals()
correctly in a hierarchy is indeed quite a pickle, it would still be nice to support inheriting other methods, for example: toString()
.
To be a bit more concrete, let's assume we have the following construct (obviously, it doesn't work because toString()
is not inherited, but wouldn't it be nice if it would?):
abstract class ResourceId(open val basePath: BasePath, open val id: Id) {
// non of the subtypes inherit this... unfortunately...
override fun toString(): String = "/${basePath.value}/${id.value}"
}
data class UserResourceId(override val id: UserId) : ResourceId(UserBasePath, id)
data class LocationResourceId(override val id: LocationId) : ResourceId(LocationBasePath, id)
Assuming our User
and Location
entities return their appropriate resource IDs (UserResourceId
and LocationResourceId
respectively), calling toString()
on any ResourceId
could result in quite a nice little representation that is generally valid for all subtypes: /users/4587
, /locations/23
, etc. Unfortunately, because non of the subtypes inherited to overridden toString()
method from the abstract base ResourceId
, calling toString()
actually results in a less pretty representation: <UserResourceId(id=UserId(value=4587))>
, <LocationResourceId(id=LocationId(value=23))>
There are other ways to model the above, but those ways either force us to use non-data-classes (missing out on a lot of the benefits of data classes), or we end up copying/repeating the toString()
implementation in all our data classes (no inheritance).
Declare properties in super-class outside of constructor as abstract, and override them in sub-class.
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()
Kotlin Traits can help.
interface IBase {
val prop:String
}
interface IDerived : IBase {
val derived_prop:String
}
data classes
data class Base(override val prop:String) : IBase
data class Derived(override val derived_prop:String,
private val base:IBase) : IDerived, IBase by base
sample usage
val b = Base("base")
val d = Derived("derived", b)
print(d.prop) //prints "base", accessing base class property
print(d.derived_prop) //prints "derived"
This approach can also be a workaround for inheritance issues with @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
You can inherit a data class from a non-data class. Inheriting a data class from another data class is not allowed because there is no way to make compiler-generated data class methods work consistently and intuitively in case of inheritance.
@Željko Trogrlić answer is correct. But we have to repeat the same fields as in an abstract class.
Also if we have abstract subclasses inside the abstract class, then in a data class we cannot extend fields from these abstract subclasses. We should first create data subclass and then define fields.
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()
}
The truth is: data classes do not play too well with inheritance. We are considering prohibiting or severely restricting inheritance of data classes. For example, it's known that there's no way to implement equals()
correctly in a hierarchy on non-abstract classes.
So, all I can offer: don't use inheritance with data classes.