Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Kotlin Value class - new kid in town?
Mahendran
Mahendran

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){...}
Enter fullscreen modeExit fullscreen mode

To make sure the caller pass in correct duration, I can do this.

funshowTooltip(message:String,durationInMillis:Long){...}
Enter fullscreen modeExit fullscreen mode

even this,

/*** Shows tooltip of message for given duration*  @param message - message to display*  @param durationInMillis - duration in milliseconds**/funshowTooltip(message:String,durationInMillis:Long){...}
Enter fullscreen modeExit fullscreen mode

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)
Enter fullscreen modeExit fullscreen mode

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))...
Enter fullscreen modeExit fullscreen mode

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.

Alt Text

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)}}
Enter fullscreen modeExit fullscreen mode

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));
Enter fullscreen modeExit fullscreen mode

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){...}
Enter fullscreen modeExit fullscreen mode

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));
Enter fullscreen modeExit fullscreen mode

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}
Enter fullscreen modeExit fullscreen mode
publicfinalclassDuration{privateDuration(longmillis){this.millis=millis;}publicfinallongcomponent1(){returnthis.millis;}@NotNullpublicfinalDurationcopy(longmillis){returnnewDuration(millis);}// Same companion byte code from normal class...}
Enter fullscreen modeExit fullscreen mode

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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
prasadhkumar profile image
prasadhkumar
  • Location
    Coimbatore
  • Work
    Senior Software Engineer at Payoda
  • Joined

Indeed an valued article thanks for the infos 👌

CollapseExpand
 
michaelndunwa profile image
MichaelNdunwa
🖥️⌨️🖱️

👍💙

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Android / Kotlin / Python / KMP
  • Location
    India
  • Work
    Senior android developer at Omnissa
  • Joined

More fromMahendran

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp