Kotlin Uncovered: Part 3
Learning about Kotlin data classes through decompilation
Black and Grey Computer Laptop by Pixabay is licensed under Creative Commons Zero
If you’re just joining us, get caught up on what it means to decompile Kotlin bytecode into Java to uncover what it’s doing by jumping to part one. If you already know about all that, you can take a look at a simple decompiled class in part two. In this post, we’ll be looking at all the benefits of using a Kotlin data class.
We’ll be using the same User
class we used before, but with the added keyword data
.
data class User( val firstName: String, var lastName: String? )
We get the same great features we got last time, plus some new ones. We’re just looking at the new features here, so if you don’t want to miss out, check out the previous post, or the full gist.
The first new thing we get is ordered component methods for each public property of User
.
@NotNull public final String component1() { return this.firstName; } @Nullable public final String component2() { return this.lastName; }
These component methods are used for destructuring class declarations.
val user = User("Victoria", "Gonda") val (firstname, lastname) = user
Then we have the copy method. copy()
is especially useful whenever you’re working with immutable objects.
@NotNull public final User copy(@NotNull String firstName, @Nullable String lastName) { Intrinsics.checkParameterIsNotNull(firstName, "firstName"); return new User(firstName, lastName); }
The copy method also has a synthetic bridge method that the JVM uses to give us the ability to override the values of each property. If you’re curious, there’s deeper JVM learning here.
// $FF: synthetic method // $FF: bridge method @NotNull public static User copy$default(User sourceUser, String firstName, String firstName, int mask, Object object) { if((mask & 1) != 0) { firstName = sourceUser.firstName; } if((mask & 2) != 0) { firstName = sourceUser.lastName; } return sourceUser.copy(firstName, firstName); }
Because of this one synthetic bridge method, we have multiple options for copying an object:
val user = DataUser(firstName = "Victoria", lastName = "Gonda") val user1 = user.copy() // User(firstName=Victoria, lastName=Gonda) val user2 = user.copy(firstName = "Tori") // User(firstName=Tori, lastName=Gonda) val user3 = user.copy(lastName = null) // User(firstName=Victoria, lastName=null) val user4 = user.copy(firstName = "Josh", lastName = "Kovach") // User(firstName=Josh, lastName=Kovach)
We also get toString()
, which clearly prints the values of each of the object’s attributes.
public String toString() { return "User(firstName=" + this.firstName + ", lastName=" + this.lastName + ")"; }
No longer do we have to figure out hashCode
. Kotlin takes care of this and gets it right! It’s so easy to get wrong when trying to implement it on your own.
public int hashCode() { return (this.firstName != null? this.firstName.hashCode():0) * 31 + (this.lastName != null? this.lastName.hashCode():0); }
The data class implements equals
as well, comparing all the class’ attributes for us.
public boolean equals(Object var1) { if(this != var1) { if(var1 instanceof User) { User var2 = (User)var1; if(Intrinsics.areEqual(this.firstName, var2.firstName) && Intrinsics.areEqual(this.lastName, var2.lastName)) { return true; } } return false; } else { return true; } }
We get roughly 80 lines of Java code from four lines of clear, readable Kotlin. Those four lines could easily be written as one line. It’s no wonder people are excited to try out Kotlin!
Need to jump around to other parts of the series? You can find them below:
Comments
Thanks for the articles! It’s nice encouragement to look under the hood a bit
I think you have a typo in your synthetic bridge method code though, it’s assigning and using firstName twice instead of lastName
Yay! This helped so much!