Anything Java can do Kotlin can do better

Zach Westlake | Pinterest engineer, Core Experience

While there’s been a lot of buzz around Kotlin lately, you may still be asking yourself why you should actually use it. What does Kotlin do that Java doesn’t? It’s good to maintain a healthy sense of skepticism for any new technology.

But here’s the secret about Kotlin–the language itself largely doesn’t do anything Java can’t. Much of what Kotlin does can be replicated in Java given enough acrobatics. It’s true that you could write all your code from scratch and ignore the plethora of tools and helpful additions Kotlin adds to the development environment. Many apps don’t use Kotlin and ship just fine. However, what Kotlin does do better than Java is speed up development by making code simple and easy to read, while eliminating some of the boilerplate code you’re used to writing.

In this post, we’ll focus on two particularly useful areas: properties and nullability in Kotlin.

Properties

Let’s start with a user model, which is a common model you might create in a new app. A user model has two fields, an email address (i.e. a String) and a user’s age (e.g. an Integer). In Java, you might be used to writing this out or having your favorite IDE auto generate it. Here’s an example of how this looks in Java:

public final class User {
 @NotNull
 private final String email;
 private final int age;

 public User(@NotNull String email, int age) {
  if (email == null) {
   throw new RuntimeException("Email can't be null");
  }
  super();
  this.email = email;
  this.age = age;
 }
 @NotNull
 public final String getEmail() {
  return this.email;
 }
 public final int getAge() {
  return this.age;
 }
 public String toString() {
  return "User(email=" + this.email + ", age=" + this.age + ")";
 }
 public int hashCode() {
  return (this.email != null ? this.email.hashCode() : 0) * 31 + this.age;
 }
 @NotNull
 public final User copy(@NotNull String email, int age) {
  Intrinsics.checkParameterIsNotNull(email, "email");
  return new User(email, age);
 }
 public boolean equals(Object otherObject) {
  if(this != otherObject) {
   if(otherObject instanceof User) {
    User otherUser = (User)otherObject;
    if(this.email.equals(otherUser.email) && this.age == otherUser.age) {
     return true;
   }
  }
   return false;
  } else {
   return true;
  }
 }
}

That’s ~50 lines of code to create the getters, setters and equal function needed for a user model. Now let’s do it in Kotlin.

data class User(val email: String, val age: Int)

Overall Kotlin wins with simplicity, with one line of code. It’s not a mistake, and this isn’t VimGolf–it’s the exact same as the Java code functionally. In fact, that’s not technically 100 percent true, because Kotlin code also protects developers at compile time from accidentally passing email as a null value.

Nullability

Let’s move on to nullability. In this case, Kotlin has null built into the type system. What does that mean exactly? When you define an object in Kotlin you must specify if you’ll allow it to be null since, by default, objects are not nullable.

For example, let’s write a simple logging class in Java that requires the user’s email in order to log an event.

public class LoggingClass {
 private final User myUser;
 public LoggingClass(User myUser) {
this.myUser = myUser;
}
 public void logEvent(String eventName) {
String userEmail = myUser.getEmail();
AnalyticsClient.logEvent(eventName, userEmail);
}

}

The problem with this Java code is the call to logEvent could cause a NullPointerException if myUser is null, resulting in a crash, so you try to fix it:

public class LoggingClass {
 private final User myUser;
 public LoggingClass(User myUser) {
this.myUser = myUser;
}
 public void logEvent(String eventName) {
if (myUser != null) {
String userEmail = myUser.getEmail();
AnalyticsClient.logEvent(eventName, userEmail);
}
}

}

If we’re honest, we’ve all seen or done this at some point. While it’s a quick short term fix, you’re just kicking the problem further down the road over the long run. The crash is fixed, but the problem still exists and now just silently fails with the side effect that we no longer correctly log events possibly leading to a painful debugging session trying to figure out why.

Let’s write the same class in Kotlin:

class LoggingClass(private val myUser: User) {
 fun logEvent(eventName: String) {
val userEmail = myUser.email
AnalyticsClient.logEvent(eventName, userEmail)
}
}

In this example, it’d be impossible (under normal conditions) to get the app to compile if the user model passed to it could be null. A nice addition to Kotlin is that the compiler will generate an error here, preventing you from creating a NullPointerException.

As shown above, the compiler won’t let us pass a user that could be null into the logger. This mean you don’t have to worry about the user object being null and you can write your logging class without worrying about the state of the user model by guarding for null values.

So what is Kotlin doing here to force a value that can’t be null? Let’s decompile our Kotlin code to see. Besides the compile time check, Kotlin also adds a runtime check:

public LoggingClass(@NotNull User myUser) {
Intrinsics.checkParameterIsNotNull(myUser, “myUser”);
super();
this.myUser = myUser;
}

You could easily create your own util class to do this in Java at runtime and with enough acrobatics write a linter to help at compile time, or you could use Kotlin to get it easily for free.

Conclusion

Kotlin enables developers to write less code, resulting in fewer bugs, time saved and less stress. Developers can spend less time writing boilerplate code or fixing NullPointerExceptions, and spend more time on product innovation and making the experience better for users.


This is the third post in our series on Kotlin. Check out these others: