
Posted on • Edited on • Originally published atmahendranv.github.io
Kotlin Value class - new kid in town?
Kotlin'sdata class
is a fan favorite when it comes to store any model. Bundled with bunch of necessary methods, devs get a lot while writing way less. FromKotlin 1.5 — we havevalue class
-s. Let's see what is a value class and where to use it.
While data class is used for holding model ,value class
adds attribute to a value and constraint it's usage. This class is nothing but a wrapper around a value, but the Kotlin compiler makes sure there is no overhead due to wrapping.
The problem
Duration is a classical problem where anyone can make mistake, if they didn't read between the lines. So, I wrote a function to show a tooltip message for given duration in millis. The signature looks like this.
funshowTooltip(message:String,duration:Long){...}
To make sure the caller pass in correct duration, I can do this.
funshowTooltip(message:String,durationInMillis:Long){...}
even this,
/*** Shows tooltip of message for given duration* @param message - message to display* @param durationInMillis - duration in milliseconds**/funshowTooltip(message:String,durationInMillis:Long){...}
Despite all the naming and documentation, someone can invoke the tool tip with seconds. After allNASA lost a spacecraft due to a unit mistake.
showTooltip("I'm going to pass duration in seconds",2L)
A no-brainer solution
To make sure, the user pass in millseconds, the duration can be wrapped in a class, and retrict the object creation to few meaningful helper methods, this wrapper class can make sure proper value passed in to the method.
classDurationprivateconstructor(valmillis:Long){companionobject{funmillis(millis:Long)=Duration(millis)funseconds(seconds:Long)=Duration(seconds*1000)}}funshowTooltip(message:String,duration:Duration){println("Will show $message for ${duration.millis} milliseconds")}...showTooltip("Hello - Seconds",Duration.seconds(2L))showTooltip("Hello - Millis",Duration.millis(1200))...
Now theshowTooltip
function takes inDuration
and process it inmillis
. This ensures the caller send proper duration to the function andshowTooltip
can rely on theDuration#millis
field.
However, for each duration usage, we box it inside a object, just to avoid communication gap. To pass in a parameter, a new object is created and allocated in memory.
Enter kotlin - value class
The Kotlin value classwraps a single value and thus impose any restriction/conversion to it. Kotlin compiler takes away the boxing in possible cases to deliver performance.
@JvmInlinevalueclassDurationprivateconstructor(valmillis:Long){companionobject{funmillis(millis:Long)=Duration(millis)funseconds(seconds:Long)=Duration(seconds*1000)}}
We don't see much difference but just two new keywords. But under the hood, a normalDuration
class and value class is different altogether. I'm leaving the value class byte code here. In case you wonder how the normal class looks in bytecode, read through this and then reach to ByteCode of normal wrapper section.
// Duration -- value class bytecodepublicfinalclassDuration{privatefinallongmillis;privateDuration(longmillis){this.millis=millis;}publicstaticfinalclassCompanion{// Comapanion that outputs Duration is mangled to return the wrapped valuepublicfinallongmillis_PZfE49U/* $FF was: millis-PZfE49U*/(longmillis){returnDuration.constructor-impl(millis);}// Comapanion that outputs Duration is mangled to return the wrapped valuepublicfinallongseconds_PZfE49U/* $FF was: seconds-PZfE49U*/(longseconds){returnDuration.constructor-impl(seconds*(long)1000);}}}// Caller function name mangledpublicstaticfinalvoidshowTooltip_fxiZ0zM/* $FF was: showTooltip-fxiZ0zM*/(@NotNullStringmessage,longduration){...}...// Invoking the function that consumes value class param.showTooltip_fxiZ0zM("",Duration.Companion.millis_PZfE49U(2000L));showTooltip_fxiZ0zM("",Duration.Companion.seonds_PZfE49U(2L));
As you see, where ever theDuration
object used, the function names are mangled and the param // output type changed toLong
.
In case you wonder why the mangling is needed here, this avoids any conflict resolution when the function signature matches the wrapped type.
// Using a wrapped value argfunshowTooltip(message:String,duration:Duration){...}// Using a primitive value as argfunshowTooltip(message:String,duration:Long){...}
If the compiler just replace the param type with wrapped primitive here, the code won't compile. So it is necessary to mangle up the name.
ByteCode of normal wrapper
publicfinalclassDuration{privatefinallongmillis;privateDuration(longmillis){this.millis=millis;}publicstaticfinalclassCompanion{// Companion function return the wrapped objectpublicfinalDurationmillis(longmillis){returnnewDuration(millis,(DefaultConstructorMarker)null);}// Companions function return the wrapped objectpublicfinalDurationseconds(longseconds){returnnewDuration(seconds*(long)1000,(DefaultConstructorMarker)null);}}}// Called function signature looks samepublicstaticfinalvoidshowTooltip(Stringmessage,Durationduration){...}// Caller isshowTooltip("",Duration.Companion.seconds(2L));showTooltip("",Duration.Companion.millis(1200L));
That's what a normal wrapper class look like. In case you wonder why did I use a normal class instead of adata class
. See below.
data classDurationprivateconstructor(valmillis:Long){// Same companion as normal class}
publicfinalclassDuration{privateDuration(longmillis){this.millis=millis;}publicfinallongcomponent1(){returnthis.millis;}@NotNullpublicfinalDurationcopy(longmillis){returnnewDuration(millis);}// Same companion byte code from normal class...}
data class
creates a copy method, which allows you to createDuration
given a long param. Our goal is to restrict the object creation to the companion methods. Also data class leaves a componentN function, which we don't need in this case. We're just trying to wrap the value here.
Endnote:
Since Kotlin 1.2.xx, we haveinline classes, theold name for the value class. Since the class is not actually inlined in comparison to the inline function, it has been renamed to value class and the inline keyword is deprecated now.
Top comments(2)

Indeed an valued article thanks for the infos 👌
For further actions, you may consider blocking this person and/orreporting abuse