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.
Classes in Kotlin solve two problems:
- They convey meaning through their name and make it easier for us to understand what kind of object is passed along.
- They enforce type-safety by making sure that an object of class A cannot be passed to a function that expects an object of class B as an input parameter. This prevents serious bugs at compile-time.
Primitive: ❌
Primitive types like Int, Boolean, String or Double also enforce type-safety (you can’t just pass a String where a Boolean is expected), but they don’t really convey a meaning (other than an object being a number of a certain format, for example).
A Double could be pretty much anything: a temperature in degrees Celsius, a weight in kilograms, or your screen’s brightness level in percent. All we know is that we’re dealing with a floating-point number with double precision (64 bits), but it doesn’t tell us what this number represents. For that reason, the semantic type-safety is lost.
Assume below example, there is a function we are about to pass two String param (username and password).
fun getAuthToken(username:String, password:String) { .... }
see to the above function we are able to pass the param like this and which is wrong doesn't care at the compiler level. (even we have named params both accepts the String), Hence here primitives fails.
val uname = "juno ravi" val pwd = "P@ssw0rd!123" val token = getAuthToken(pwd, uname)
This programming error cannot be detected at compile-time and will most likely lead to unexpected behavior. 💥
There are different approaches to solving the two problems mentioned above. We could just wrap a primitive type in a class, but that comes with a lot of overhead. So let’s see how we can tackle these problems above with
- data classes, 😈
- type aliases, 😜
- and value classes 😎
Data classes: Instantiating data classes is expensive. Primitive values can be written to the stack which is fast and efficient. Instances of data classes are written to the heap, which takes more time and memory.
data class Username(val username: String) data class Password(val password: String) fun getAuthToken(username:Username, password:Password) { .... }
with the above implementation is able to achieve but it is too costly process, comparatively if primitive took 2.x milliseconds means, it will take 8.x milliseconds for the single process. Hence it is not advisable to do 😈
Type aliases: From the below Example, String & Username and Password are synonyms.
Whenever the compiler sees Username/Password, it basically replaces it with String and moves on.
typealias Username = String
typealias Password = String
Since our new Username/Password type alias is now equal to String, hence it works as same as primitvies which we have seen above, it also receives the same optimization treatment as Primitive but loses the typesafety. 😜
Value classes: It looks pretty similar to data classes. The signature looks exactly the same, except that instead of data class the keyword is value class, also there should be @JvmInline annotation to that class due to the Valhalla JVM release in future (currently JVM supports value class on the built in primitive types). Important thing is the value class accepts only one constructor param.
Since this value class acts as a wrapper to that variable, it also receives the same optimization treatment as Primitive which we can see it on the below screenshot on the highlighted section of the Kotlin byte code where as uname is referred it as Username class type, but pwd is referred as String value type, because it is defined with a value class 😎
Side Note:
Since Kotlin 1.2.xx, we have inline classes, the old 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 from Kotlin 1.5
Happy Coding :-)
No comments:
Post a Comment