Data classes vs value-based clasess

I’m quite new to both Java and Kotlin (I’ve got C# background) and I’ve learned of a Java concept of Value-based Classes that is realized in some standard Java Class Library types (i.e. java.time.ZonedDateTime). I believe this is something roughly similar in purpose to C#'s structs and can be used to model small domain types.

Now Kotlin’s data classes with all-val props seem to perfectly fit all the value-based class requirements but one: instantiation via factory methods instead of constructors. The factory method approach allows some optimization where popular instances of a value-based classes can be cached and re-used (correct me if I’m wrong), and this also justifies the other requirement about value-based equality as opposed to reference-based equality. So how do I achieve that with a data class in Kotlin (private ctors + factory methods)?

I wanted to make data class’s primary constructor private to hide its constructor, but IDEA pops out and warns me:


Data class must have all pros defined in its primary constructor, the primary constructor is required not private because the generated copy() method will expose its primary constructor. Data class can’t hide all its constructors.
I always treat data class as something that sounds not bad but never works well. I suggest you not pay so much attention into data class. If you want a value-base class, just write it as how you write with Java.

Kotlins data classes are indeed a good fit for “value based” classes as described on the page you’ve linked. However technically value classes are not value types, what would be a struct in C#. Kotlin’s value classes are still reference types and not something like an Int or so. This may change with Java 10, when value types should be introduced to the JVM.

To the point of @CNife: You can declare the constructor of a value class private and provide a factory method:

>>> data class Point private constructor(val x: Int, val y: Int) {
...     companion object {
...         fun create(x: Int, y: Int) = Point(x, y)
...     }
... }
>>> Point.create(1, 2)
Point(x=1, y=2)

But, as he said, the copy method would still expose this constructor indirectly. It is a bit sad, that a data class is not allowed to implement its own version of copy.

This would be possible in Scala:

object HelloWorld extends App {
    val p1 = Point.create(1, 2)
    val p2 = p1.copy(y = 99)
    println(p1)
    println(p2)
}

case class Point private(x: Int, y: Int) {
    def copy(x: Int = this.x, y: Int = this.y) = Point.create(x, y)
}

object Point {
    def create(x: Int, y: Int) = new Point(x, y)
}

The intersting question is: why is it not allowed to implement copy in Kotlin?

2 Likes

Relevant bug report: https://youtrack.jetbrains.com/issue/KT-11914

There is nothing preventing you from having a factory method or predefined constants. However you can’t “override” copy currently. You can either live with the visible constructor and tell users to use the factory method or you can do what I did in this case and not make it a data class (and implement toString, equals, componentX and so on manually).

1 Like

Thanks for all the replies, I guess I’ll sacrifice perfect adherence to the concept of value-based classes in favor of the Kotlin’s concise syntax of data classes (which I love BTW!). Still I can’t wait for the value types to arrive to JVM in Java 10 - I’m wondering how (and if) that will change Kotlin syntax.

You will have to wait for a long time. As far as I understand, project Valhalla partially breaks the bytecode, so it will be a very long time before Java community will accept that.
If there is one bad thing about Java ecosystem, it is that any new good things take an insane amount time to take place (Java 9 was delayed for what? two years? And what about Lambdas on Android?).

Are there any updates on this?

Maybe we can have a way to tell the compiler not to create the copy method at all? then we can manually provide it if desired.

Just released a compiler plugin that helps with this issue:

What do you think about make copy visibility the same as the constructor?

In fact copy function is a “kind” of constructor, so if you set your constructor visibility to internal or private, the visibility of the copy function should be at the same level.

This will allow you to do whatever you want inside your module, but not “escape abstraction” outside, and the same inside the companion object.