Search code examples
kotlindata-classkotlin-java-interop

Kotlin data classes with Java super class


I have a Java class that holds generic information on databse entities (i.e. their id).

@Data
public class DbEntity {
    protected final String id;

    public DbEntity(String id) {
        this.id = id;
    }
}

We use Lombok @Data to generate getters, toString, equals...

In Java I would simply extend this class and add @Data once again.

@Data
class JavaSubClass extends DbEntity {

    public JavaSubClass(String id) {
        super(id);
    }
}

In a newer service we use Kotlin but would like to reuse standard classes such as DbEntity.

My first approach was to simply declare a data class such as

data class SubClass1(val id: String, val name: String) : DbEntity(id)
Accidental override: The following declarations have the same JVM signature (getId()Ljava/lang/String;):
  fun <get-id>(): String defined in com.demo.SubClass1
  fun getId(): String! defined in com.demo.SubClass1

After some reading I found several solutions, all of which I'm not super happy with.

  1. Don't use data classes. This works but leaves me with the task of implementing equals etc.
class SubClass4(id: String, val name: String) : DbEntity(id)
  1. Duplicate the field. This works but we end up with two fields that could go out of sync.
data class SubClass3(val subId: String, val name: String) : DbEntity(subId)
  1. Assign a different name to the getter. This fundamentally also duplicates the field, but hides the getter.
data class SubClass2(@get:JvmName("getId_") val id: String, val name: String) : DbEntity(id)

As I said, I'm not happy with any of the solution presented above. Having an abstract super class or an interface instead would certainly be more appropriate. However the Entity class resides in a library that primarily Java projects depend on. I'm hesitant to change it just because of a new Kotlin dependnecy.

Did anyone encounter similar issues and has advice on how to solve them?


Solution

  • As a workaround, until KT-6653 - Kotlin properties do not override Java-style getters and setters is fixed, I would go for a variant of your point 3, i.e.:

    data class SubClass(@get:JvmName("bogusId") private val id: String, val name: String) : DbEntity(id)
    

    The benefit of this variant is, that you always access the "original" getId-function. You will not use the bogusId()-function as it is not visible/accessible (accessing it via reflection makes no sense... you are only interested in the actual id-field). This works and looks similar for both sides: from Java as also from Kotlin. Still, under the hood this variant uses 2 fields, but in the best case you can just replace it in future with something like:

    data class SubClass(override val id: String, val name : String) : DbEntity(id)