Data class inheritance

Why can not I inherit a lot of data class from another data class, in my case in the set of data classes there are several identical fields and I want to put them in a separate class and inherit from it, but the Kotlin does not allow it.

General class

@XmlAccessorType(XmlAccessType.PROPERTY)
data class Model(@XmlElement var name: StringProperty, @XmlElement var id: StringProperty) {

    constructor() : this(SimpleStringProperty(), SimpleStringProperty())

    fun getName(): String {
        return name.get()
    } 

    fun setName(name: String) {
        this.name.set(name)
    }

    fun getId(): String {
        return id.get()
    } 

    fun setId(name: String) {
        this.id.set(name)
    }
}

//@XmlAccessorType(XmlAccessType.PROPERTY)
//open class Model(@XmlElement var name: StringProperty, @XmlElement var id: StringProperty) {
//
//    constructor() : this(SimpleStringProperty(), SimpleStringProperty())
//
//    fun getName(): String {
//        return name.get()
//    }
//
//    fun setName(name: String) {
//        this.name.set(name)
//    }
//
//    fun getId(): String {
//        return id.get()
//    }
//
//    fun setId(name: String) {
//        this.id.set(name)
//    }
//}

Other class

@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
data class MyOtherData(@XmlElement val service: StringProperty) : Model() {

    constructor() : this(SimpleStringProperty())

    fun getService(): String {
        return service.get()
    }

    fun setService(service: String) {
        this.service.set(service)
    }
}

In the future, I need to copy the data class and serialize it in Xml, to save it to a file.

Xml serializer

object Xml {

    fun serialize(value: Any): String {

        val context = JAXBContext.newInstance(value::class.java)

        val marshaller = context.createMarshaller()

        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)

        StringWriter().use { stream ->

            marshaller.marshal(value, stream)

            return stream.toString()
        }
    }

    fun <T> deserialize(value: String, clazz: Class<T>): T = StringReader(value).use { stream -> return JAXB.unmarshal(stream, clazz) }
}

Main.kt

val model = MyOtherData()

model.setService("OTHER_SERVICE")

model.setName("OTHER_NAME")

model.setId("3fad6db0ae684a0699c8ff0898e46d55")


// serialization/deserialization XML

val xml: String = Xml.serialize(model)

println(xml)

val des = Xml.deserialize(xml, MyOtherData::class.java)


val copy = model.copy()

println(copy.getService())

println(copy.getName())

println(copy.getId())

Can make a copy through serialization/deserialization, but i thought the data classes can be inherited.

1 Like

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.

3 Likes

If I inherit from a class, copying does not work correctly, no properties are copied that are in a class other than data

Yes, that’s correct. It’s impossible to generate a copy() method that could copy the properties of a non-data class, because there is no way to establish the correspondence between constructor parameters and properties.

4 Likes

Ok, I understand you, then I’ll have to use this

object Xml {

    ...

    fun <T> copy(value: Any, clazz: Class<T>): T = deserialize(serialize(value), clazz)
}

Thanks for the help.

Are there any examples of this that you can link to? I have tried several variations, but each one seems to give a different compiler error. Property is hidden, property needs override, property is final and cannot be overridden, etc.

I don’t have examples at the moment.

Yes, I realize that I’m replying to an old post.

EDIT: I went back and tried again. Used the IntelliJ tools to fix the problem. The properties in the base class had to be declared as open in order to inherit properly.

1 Like

I faced a similar issue when (de)serializing to JSON with RuntimeTypeAdapterFactory. Maybe an interface will suit your purposes.

interface BasicData {
    val id: String
}

data class AnotherDataClass(
    override val id: String,
    ...
} : BasicData
2 Likes

Is there any way to fix the comment section problem?
I changed Parent to sealed class, but still, it’s the same.

package day1

object Main {
    @JvmStatic
    fun main(args: Array<String>) {

        val f1 = Foo(1)
        Thread.sleep(3)
        val f2 = Foo(1)

        val p1 = (f1 as Parent)
        val p2 = (f2 as Parent)

        println(p1 == p2) // true
        println(p1.b == p2.b) // false
    }
}

data class Foo(val a: Int) : Parent("$a-${System.currentTimeMillis()}")

open class Parent(val b: String) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Parent

        if (b != other.b) return false

        return true
    }
    override fun hashCode(): Int {
        return b.hashCode()
    }
}


When I override Foo.equals() and Foo.hashCode() then it works correctly. So I guess, probably default generated code for data class is wrong.

    package day1

    object Main {
        @JvmStatic
        fun main(args: Array<String>) {

            val f1 = Foo(1)
            Thread.sleep(3)
            val f2 = Foo(1)

            val p1 = (f1 as Parent)
            val p2 = (f2 as Parent)

            println(p1 == p2) // false
            println(p1.b == p2.b) // false
        }
    }

    data class Foo(val a: Int) : Parent("$a-${System.currentTimeMillis()}"){
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false
            if (!super.equals(other)) return false

            other as Foo

            if (a != other.a) return false

            return true
        }

        override fun hashCode(): Int {
            var result = super.hashCode()
            result = 31 * result + a
            return result
        }
    }

    sealed class Parent(val b: String) {

        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false

            other as Parent

            if (b != other.b) return false

            return true
        }
        override fun hashCode(): Int {
            return b.hashCode()
        }
    }


Not wrong, just not suitable for your case.

Data classes are intended for simple value classes whose state is entirely characterised by the properties in the constructor. If that doesn’t apply, then a data class may not be a good fit, because the autogenerated equals(), hashCode(), toString(), copy(), and componentX() methods may not do what you want.

2 Likes