Kotlin Uncovered: Part 2

Learning about Kotlin classes through decompilation

41.jpg by Startup Stock Photos is licensed under CC0

One of the tools we can use to learn about Kotlin is decompilation. Because Kotlin compiles down to bytecode, we can decompile it into Java to learn some interesting things. If you missed my first blog post introducing this topic, you can get caught up here.

The first thing I tried decompiling to Java was a simple class, and the results were exciting. Let’s look at a User class with a firstName and lastName. Both attributes are Strings. The first name is non-null and immutable, while the last name is nullable and mutable.

class User(
  val firstName: String,
  var lastName: String?
)

That’s the entire class. By using val and var we make the fields immutable and mutable, respectively. Then, the question mark after String for lastName makes it nullable. Let’s put this through the decompiler.

For the full class, check out this gist. We’ll break down the individual parts here.

The first thing to notice is the class declaration. Kotlin classes are final by default, so you can’t extend from them. Immutability is a great practice to follow. By using immutability, we facilitate simpler classes and thread-safe objects. It makes for a couple more steps when working with libraries that depend on classes being extendable, such as declaring it as open, but the trade-off is worth it.

public final class User {

Then, we have the fields. Notice they are private by default, which is great. Also notice that the firstName is final, which reflects the immutability we wanted. Lastly, we get the @NotNull and @Nullable annotations.

@NotNull
private final String firstName;
@Nullable
private String lastName;

Here is our constructor. Notice the first line of the body: checkParameterIsNotNull(). With nullability built into the type system, we are usually pretty safe with Kotlin. But what about if we are calling it from Java? Kotlin does an extra check for us just in case. Not only does it make this check if the parameter is null when it shouldn’t be, but it also gives us a stack trace that clearly shows what was null and where it was.

public User(@NotNull String firstName, @Nullable String lastName) {
  Intrinsics.checkParameterIsNotNull(firstName, "firstName");
  super();
  this.firstName = firstName;
  this.lastName = lastName;
}

Finally, we have our getters and setters. Notice that they reflect our desired immutability. There is a getter and setter for lastName, but there is only a getter for firstName. We also have the @NonNull and @Nullable annotations. You’ll continue to see those all over as we decompile more examples.

@NotNull
public final String getFirstName() {
  return this.firstName;
}

@Nullable
public final String getLastName() {
  return this.lastName;
}

public final void setLastName(@Nullable String var1) {
  this.lastName = var1;
}

Very quickly we can start to see the work Kotlin does for us in reducing boilerplate code. We get so much from a few lines of code, which only contains the information we care about, thus reducing the cognitive overhead.

We got a lot from investigating this class, and we can get even more by making it a data class. Catch the next blog post in this series to see the features we get by adding the single keyword: data.


Need to jump arond and check out other parts of the series? You can find them below:

Part One

Part Three

Photo of Victoria Gonda

Victoria is a software developer working on mobile and full stack web applications. She enjoys exchanging knowledge through conference talks and writing.

Comments

  1. Madsz
    May 22, 2017 at 17:53 PM

    This is soooo good, but how do we keep up with knowing when you post the next one??

  2. Karen F.
    May 23, 2017 at 0:49 AM

    Really enjoying your series on Kotlin! Very well explained, and makes me excited to learn more!

  3. AG
    May 23, 2017 at 16:51 PM

    How does the constructor allow something else before calling super()?

  4. May 23, 2017 at 21:37 PM

    @AG the constructor can do “something” because the call is static:

    Intrinsics.checkParameterIsNotNull(firstName, “firstName”);

    Nothing there depends on the current type being created yet. The “firstName” string is passed in the constructor, therefore available.