Feature request: A modifier annotation for data classes to provide a non arg constructor on JVM


#1

Problem:

I would to build an immutable domain model, and data classes are perfect for that.
JPA specifies that all entities should provide a non arg empty constructor for reflection.

Currently, on Kotlin data classes, there’s two ways:

  1. Provide default values for all argument in constructor.
  2. Provide a secondary non arg constructor, calling the primary constructor passing imaginary values.

In my opinion, the number 1 solution isn’t a good design to prevent inconsistent object state.
The solution number two is an inelegant hack.

The possible solution:

I think, that a good solution would be an additional modifier annotation, to build a data class able to reflection, generating an empty constructor on JVM, initializing all properties with the same principle of lateinit. This will not compromise the nullability.


#2

Looks like you’re contradicting yourself. A data class with a default no-arg constructor that does not set any initial values to any properties is anything but immutable. And there is absolutely no way to prevent inconsistent object state if we generate such a constructor.


#3

It’s possible that the “secondary non arg constructor” can be done with Kotlin data class’s copy feature, which gives you new instances with changed values.


#4

You’re right!

Let me clarify my propose:

Using JPA, I can build immutable entities with data classes (using only val properties), see this:

This works very fine! See my entire demo on github.

Look that, although immutable and instantiated by a secondary private non arg constructor (calling the primary, providing imaginary values), JPA makes a magic trick and delivers me a beautiful entity with all correct values.

Even though, id is a val, JPA will save the correct value id on data base.

Sounds crazy but, this entity is immutable to me, is immutable to compiler, but isn’t immutable to JPA. (It’s a desired behavior.)

I’m assuming that JPA will never deliver me an entity inconsistent (i.e. with the default values provided by me), always reflecting all entity properties values to the correct ones.

My propose is:

  1. A modifier annotation generates an additional non arg constructor only in bytecode .class
  2. This constructor only can be called by reflection.
  3. When this constructor is called, they instantiate an object with all properties no initialized. (Kotlin lateinit principle). JPA don’t care about this.
  4. In this moment, if any property is accessed, it should throw a special exception that clearly identifies the property being accessed and the fact that it hasn’t been initialized. Like lateinit. We could say that in this particular moment, the object is in limbo.
  5. Who called this constructor (by reflection), either JPA or whatever, should be responsible to provide a value through reflection to every property before delivers the object. JPA already did this.

I just want to suppress the necessity of write a secondary private non arg constructor passing imaginary arguments to primary.


Kotlin 1.0.6 EAP
#5

It’s possible that the “secondary non arg constructor” can be done with Kotlin data class’s copy feature, which gives you new instances with changed values.

I’m sorry! I don’t understand. Could you exemplify that? Thank you!


Using Sugar ORM with Kotlin
#6

There is no possibility to enforce that a constructor can only be called by reflection. If the constructor is in the .class file, then it will be visible to Java code, and Java code will be able to call it.


#7

I understand. Thank you for your reply!


#8

In really, there’s one way to prevent that java code call this constructor.

-> Just set it private.

So, this become accessible only by reflection:

Constructor constructor = Person.class.getDeclaredConstructor(new Class[0]);
constructor.setAccessible(true);
Person person = constructor.newInstance(new Object[0]);


Using Sugar ORM with Kotlin
#9

Yes, I know, thanks. Such a constructor will not work with JPA, which defeats the purpose of your suggestion.

In any case, adding a language feature in order to satisfy the specific idiosyncrasies of a specific framework does not seem to be a reasonable idea.


#10

I didn’t knew that JPA Specs specifies that, because I already did this on Hibernate.

But now, I discovered that Hibernate accepts a private constructor, but it isn’t efficient like public or protect.

Anyway, you’re right. Thanks for your attention.