349

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 convenient way to extend a data class. What I need is something like this:

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

The code above fails because of clash of component1() methods. Leaving data annotation in only one of classes does not do the work, too.

Perhaps there is another idiom to extend data classes?

UPD: I might annotate only child child class, but data annotation only handles properties declared in the constructor. That is, I would have to declare all parent's properties open and override them, which is ugly:

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()
Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
Dmitry
  • 4,232
  • 2
  • 15
  • 13
  • 3
    Kotlin implicitly creates methods `componentN()` that return value of N-th property. See docs on [Multi-Declarations](http://kotlinlang.org/docs/reference/multi-declarations.html) – Dmitry Oct 20 '14 at 09:27
  • For opening the properties, you can also make Resource abstract or use compiler plugin. Kotlin is strict about open/closed principle. – Željko Trogrlić Nov 06 '17 at 08:12
  • @Dmitry Since we could not extend a data class, would your "solution" of keeping the parent class variable open and simply overriding them in the child class an "ok" work around? – Archie G. Quiñones Jan 30 '19 at 05:57
  • There cannot be a open data class – Rohit gupta Jul 03 '23 at 10:01

14 Answers14

324

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.

Andrey Breslav
  • 24,795
  • 10
  • 66
  • 61
  • Hey Andrey, how does equals() as it is generated on data classes work now? Does it only match if the type is exact and all the common fields are equal, or only if the fields are equal? It does seem like, because of the value of class inheritance for approximating algebraic data types, it might be worth coming up with a solution to this problem. Interestingly, a cursory search revealed this discussion on the topic by Martin Odersky: http://www.artima.com/lejava/articles/equality.html – orospakr Mar 29 '15 at 04:19
  • 12
    I don't believe there's much of a solution to this problem. My opinion so far is that data classes must not have data-subclasses at all. – Andrey Breslav Mar 30 '15 at 10:01
  • 10
    what if we have a library code such as some ORM and we want to extend its model to have our persistent data model? – Krupal Shah Dec 12 '15 at 14:36
  • @KrupalShah it may be that in this case data classes won't help you. – Andrey Breslav Dec 13 '15 at 09:52
  • @AndreyBreslav I think you have already put the discussion thread on blog. By the way, there should be a middle way (no more annotations and keywords please). – Krupal Shah Dec 13 '15 at 10:01
  • 3
    @AndreyBreslav [Docs on Data classes](https://kotlinlang.org/docs/reference/data-classes.html) do not reflect the state after Kotlin 1.1. How do Data classes and inheritance play together since 1.1? – Eugen Pechanec May 07 '17 at 06:45
  • 4
    @EugenPechanec See this example: https://kotlinlang.org/docs/reference/whatsnew11.html#sealed-and-data-classes – Andrey Breslav Aug 08 '17 at 09:09
  • 12
    if we can't use inheritance for data classes it means lots of duplicate code when logic is the same and data is different....i am duplicating lots of code for this lack of inheritance support, very very bad – S.Bozzoni Sep 07 '20 at 10:12
  • @AndreyBreslav maybe mixins are a solution? I think in terms of `data class` most often we want to avoid fields duplication, then methods, but with mixins it would be also possible – Witold Kupś Nov 13 '20 at 10:17
  • With Lombok plugin yo can use inheritance of java beans. It generates equals() methods with calling super(). What is the problem with that solution? Works fine for us. – psyskeptic Apr 10 '21 at 09:41
  • Hi. Please see my recent comment in the [answer below](https://stackoverflow.com/a/45756617/3808228) – Chisko Jan 05 '22 at 04:48
  • With lombok we can have data-subclass and it models common fields very well... – ch271828n Jan 25 '22 at 13:37
  • @S.Bozzoni consider composition over inheritance. – Monkey Supersonic Mar 29 '23 at 14:24
  • @S.Bozzoni The restriction of not having proper equality in a tree of non-abstract classes is a mathematical problem that is unrelated to Kotlin or data-classes. If you want inheritance-trees do not define equality on every level. If you do not want to define equality, then you should not use a data-class as it defines equality automatically. – julaine Jun 23 '23 at 06:56
  • @julaine mmmh it sounds intresting what you are saying, I wonder if there could have been a good compromise. When coping with data we need both inheritance and equality but not on all the leves of the hierarchy, just on the leaves of the tree. I guess we could do it by hand using interfaces, but there's not an automatic mechanism provided by the framework to generate equalities between leaf classes. – S.Bozzoni Jun 27 '23 at 07:05
  • @S.Bozzoni as Zeljko's answer shows, a dataclass can have an abstract dataclass (that is not a baseclass). That will provide your equality on leaf classes if I understand your problem correctly. – julaine Jun 28 '23 at 05:54
  • @julaine yes i have seen Zeljko's answer and i have used it in my projects, but you have to repeat all your shared variables across the hierarchy and that is really terrible for a lot of reasons. Not a good solution at all. I would aspect much more from kotlin. – S.Bozzoni Jun 28 '23 at 09:42
203

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()
Željko Trogrlić
  • 2,765
  • 1
  • 16
  • 20
  • 41
    this does seem to be the most flexible. I do dearly wish we could just have data classes inherit from each other though... – Adam Sep 01 '17 at 00:26
  • Hello Sir, thanks for the neat way of handling Data Class Inheritance. I am facing an issue when I use the abstract class as a Generic Type. I get a `Type Mismatch` error : "Required T, Found : Resource". Can you please tell me how can it be used in Generics? – ashwin mahajan Feb 18 '19 at 12:45
  • I would also like to know if generics are possible across abstract classes. For example what if location is a String in one inherited data class and a custom class (lets say `Location(long: Double, lat: Double))` in another? – Robbie Cronin Mar 11 '19 at 03:43
  • 2
    I almost lost my hope. Thanks! – Michał Powłoka Apr 18 '19 at 12:19
  • 7
    Duplicating the parameters seems to be a poor way of implementing inheritance. Technically, since Book inherits from Resource, it should know that id and location exist. There shouldn't really be a need to have to specify those. – Johann Apr 30 '20 at 13:40
  • 1
    @AndroidDev they do not exist as they are abstract. – Željko Trogrlić Jun 13 '20 at 13:37
  • 2
    This works for Kotlin but if I have to call the data class constructor from Java I get a "cannot inherit from final [class]" compilation error. Why is this and can it be solved? – Chisko Jan 05 '22 at 04:46
51

Above solution using abstract class actually generates corresponding class and let the data class extends from it.

If you don't prefer abstract class, how about using an interface?

Interface in Kotlin can have properties as shown in this this article..

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

I was curious how Kotlin compile this. Here's equivalent Java code (generated using the Intellij [Kotlin bytecode] feature):

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(), ...
}

As you can see, it works exactly like a normal data class!

Tura
  • 1,227
  • 11
  • 22
26

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
Jegan Babu
  • 1,286
  • 1
  • 15
  • 19
  • 1
    Does this work with Room? – Chisko Jan 06 '22 at 04:34
  • This is the best answer in my eyes and here's what I think would be this solution for the original example in the question: `interface IsResource { var id: Long var location: String } data class Resource (var id: Long = 0, var location: String = ""): IsResource data class Book (var isbn: String, val resource: Resource) : IsResource by resource` – Robert Pazurek Jul 09 '22 at 15:49
6

You can inherit a data class from a non-data class.

Base class

open class BaseEntity (

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

child class

@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()

It worked.

tim4dev
  • 2,846
  • 2
  • 24
  • 30
  • 5
    Except that now you can't set the name and description properties, and if you add them to the constructor, the data class needs val/var which will override the base class properties. – Brill Pappin Jun 03 '20 at 20:05
  • 6
    Unfortunately, `equals()`, `hashCode()` and `toString()`, which will be generated for that data class, will not include properties from the base class. Which eliminates the benefits of using data class here. – Roman_D Aug 26 '20 at 12:16
5

@Ž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()
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
  • We could move History.Errors to AbstractClass.Errors.Companion.SimpleErrors or outside and use that in data classes rather than duplicating it at each inheriting data class? – TWiStErRob Jun 20 '19 at 07:48
  • @TWiStErRob, glad to hear such a famous person! I meant that History.Errors can change in every class, so that we should override it (for instance, add fields). – CoolMind Jun 20 '19 at 07:59
5

How I did it.

open class ParentClass {
  var var1 = false
  var var2: String? = null
}

data class ChildClass(
  var var3: Long
) : ParentClass()

It's working fine.

Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
shaby
  • 1,301
  • 1
  • 15
  • 17
4

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.

Abraham Mathew
  • 2,029
  • 3
  • 21
  • 42
2

As usual, when inheritance becomes problematic, the solution is composition. See Prefer composition over inheritance?.

If you just want to "extend" your class with a few additional fields, you can use composition along with some additional getters for convenience:

data class Book(
  val id: Long,
  val isbn: String,
  val author: String,
)

data class StoredBook(
  val book: Book,
  val version: Long,
  val createdAt: ZonedDateTime,
  val updatedAt: ZonedDateTime,
) {
  // proxy fields for convenience
  val id get() = book.id
  val isbn get() = book.isbn
  val author get() = book.author
}

This delegates Book properties to the book instance, so that a StoredBook can be used just like a Book in most cases, but you can still have some type-safety around whether you are dealing with an intermediate Book state or a persisted StoredBook.

To take it a step further, you could create a StoredResource interface for any stored database entry:

interface StoredResource {
  val id: Long
  val version: Long
  val createdAt: ZonedDateTime
  val updatedAt: ZonedDateTime
}

data class Book(
  val id: Long,
  val isbn: String,
  val author: String,
)

data class StoredBook(
  val book: Book,
  override val version: Long,
  override val createdAt: ZonedDateTime,
  override val updatedAt: ZonedDateTime,
) : StoredResource {
  override val id get() = book.id
  val isbn get() = book.isbn
  val author get() = book.author
}
morsecoder
  • 1,479
  • 2
  • 15
  • 24
1

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).

Khathuluu
  • 114
  • 3
0

Another way to implement inheritance is to use class with abstract values

sealed class Parent {
    
    abstract val someVal: String
    
    data class Child1(override val someVal: String) : Parent()
    
    data class Child2(override val someVal: String) : Parent()
}
SerjantArbuz
  • 982
  • 1
  • 12
  • 16
psyskeptic
  • 286
  • 2
  • 4
  • 17
0

The data class I wanted to inherit from turned out to have no behavior that shouldn't be encapsulated in an interface. With a private data class for "common" implementors of the interface, all objects can have the benefits of data while it feels like UnitVector extends V.

interface Vector {
    companion object {
        fun build(x : Float ...) : Vector = V(x ...)
        private data class V(override val x : Float ...) : Vector
    }
    val x : Float
    //functions, y etc. 
} 
data class UnitVector(override var x : Float ...) : Vector {
    init {
        //special behavior
    } 
}
clwhisk
  • 1,805
  • 1
  • 18
  • 17
-1
data class User(val id: Long, var name: String)

fun main() {
    val user1 = User(id:1, name:"Kart")
    val name = user1.name
    println(name)
    user1.name = "Michel"
    val user2 = User(id:1, name:"Michel")
    println(user1 == user2)
    println(user1)
    val updateUser = user1.copy(name = "DK DK")
    println(updateUser)
    println(updateUser.component1())
    println(updateUser.component2())
    val (id, name) = updateUser
    println("$id,$name")
}

//here is the output below

Check the image why it shows error id:1 (compiler says that use = instead of double dot where I insert the value)

SerjantArbuz
  • 982
  • 1
  • 12
  • 16
  • Hey, I see you are new here. Please be more detailed when answering a question. Checkout https://stackoverflow.com/help/how-to-answer on how to answer questions. – Len_X Jul 05 '21 at 06:07
-5

I found the best way for having an option to use inheritance in DTO is to make data classes in java with Lombok plugin.

Dont forget to place lombok.equalsAndHashCode.callSuper to true in annotation

psyskeptic
  • 286
  • 2
  • 4
  • 17