What Kotlin Does to Your Java

 

Modern-Day Development Strategies with Realm

Now that you're confidently replacing your Android app's Java code with readable, interoperable Kotlin, keep up the momentum by finding a more efficient way to handle your data changes! Realm supports all of your favorite languages (Kotlin included) to enable developers to create faster, more robust mobile apps.

Kotlin does a lot for us in the way of reducing boilerplate. But what is it really doing? We will be inspecting some decompiled Kotlin to discover how it does its job.


Introduction

My name is Victoria Gonda, and I am a software developer at Collective Idea. We are a consulting company in Holland, Michigan. We provide custom software solutions for our clients.

One of the things that interested me the most when I started programming was learning about all the different programming languages. When I found out about Kotlin, I was super excited to get going at it. I didn’t have many expectations of what it was like. Now, I would take working with Kotlin over Java any day.

What is Kotlin

What is Kotlin? Kotlin is a statically typed programming language for the JVM, Android, and the browser. Let’s dig in a little bit more to what this means.

It’s statically typed, so we have the same type safety that we do in Java. This means that we get great auto complete. Because it’s statically typed, this can inform the IDE about what’s possible.

Compared to Java, types can also be inferred. We don’t have to specify if something is a string if it’s clear. In Java, this has to be done: we have to say String name = "Victoria"; whereas in Kotlin we can merely do name = "Victoria" because it’s clear it’s a string. Semi-colons are unnecessary, and the keyword val acts as a final declaration in Java.

Null safety is built into the type system. As such, we always know when and if something can be null, and the compiler will force us to check for it. This means we can say goodbye to NullPointerExceptions.
I also enjoy Kotlin’s boilerplate production and its functional language features. Kotlin is much more concise than Java, but in a good way. It’s not terse the way some other languages can be, making it so short that it becomes unreadable.

Kotlin is also interoperable with Java, and it compiles down to byte code for the JVM to run.

Examples

A simple class was the first thing I tried decompiling. Here, there is a user with a first and last name that are strings where the first name is immutable and cannot be null.

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

The question mark after string for lastname makes it nullable.

Let’s put the class through the decompiler so that we can see the result:

public final class User {
 	@NotNull
 	private final String firstName;
 	@Nullable
 	private String lastName;

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

 	@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;
 	}
}

Notice is that the class is final by default, so you can’t extend it. Immutability can be a great tool for making classes simpler and thread safe.

The fields are private by default. Firstname is immutable, so it’s marked as final. There are also the nullable and nonnull annotations. In the constructor, in the first line in the body, the check parameter is not null. Nullability is built into the type system, so you’re usually safe.

If you call Kotlin code from Java, Kotlin does a check for us here just in case.

public static void checkParameterIsNotNull(
 		Object value, String paramName) {
 	  if (value == null) {
 		// prints error with stack trace
 		throwParameterIsNullException(paramName);
 	  }
 	}

It explicitly points us to exactly what and where something was null when it wasn’t supposed to be.

Caused by:
	java.lang.IllegalStateException: firstName must not be null
	at com.project.User.<init>(User.kt:8)

We can make this class into a data class. This is done simply by adding the keyword data at the beginning of the class declaration.

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

This is what the Java code looks like:

public final class User {
 	@NotNull
 	private final String firstName;
 	@Nullable
 	private String lastName;
 	public User(@NotNull String firstName, @Nullable String lastName) {
 		Intrinsics.checkParameterIsNotNull(firstName, "firstName");
 		super();
 		this.firstName = firstName;
 		this.lastName = lastName;
 	}
 	@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;
 	}
 	@NotNull
 	public final String component1() {
 		return this.firstName;
 	}
 	@Nullable
 	public final String component2() {
 		return this.lastName;
 	}
 	@NotNull
 	public final User copy(@NotNull String firstName, @Nullable String lastName) {
 		Intrinsics.checkParameterIsNotNull(firstName, "firstName");
 		return new User(firstName, lastName);
 	}
 	// $FF: synthetic method
 	// $FF: bridge method
 	@NotNull
 	public static User copy$default(User var0, String var1, String var2, int var3, Object var4) {
 		if((var3 & 1) != 0) {
 			var1 = var0.firstName;
 		}
 		if((var3 & 2) != 0) {
 			var2 = var0.lastName;
 		}
 		return var0.copy(var1, var2);
 	}
 	public String toString() {
 		return "User(firstName=" + this.firstName + ", lastName=" + this.lastName + ")";
 	}
 	public int hashCode() {
 		return (this.firstName != null?this.firstName.hashCode():0) * 31 + (this.lastName != null?this.lastName.hashCode():0);
 	}
 	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 had the same things before with some other additions. There are components that are used for de-structuring and class declarations.

There’s a copy method. Copy methods can be really useful when working with immutable types. For copy, there’s also a synthetic bridge method. This gets into how the JVM handles classes and such.

There’s a toString, which clearly prints out all the variables and what they are, additionally, hashCode and equal methods.

One more thing before moving on is that we can declare default values.

data class User(
 val firstName: String = "Victoria",
 var lastName: String?
)

Here, if a first name is not provided, just use the string Victoria. We can pair default values with named parameters to get a substitution for builders. Here’s an example where we are clearly creating an object, but we’re only providing what we care about.

val user = User(
 lastName = "Gonda"
)

Notice that firstname is left off. You can exclude any of the variables that either have a default value or can be null.

Null Safety

One of the biggest things that we hear about Kotlin is this null safety. You notate that a variable is nullable with a question mark.

// Wont compile
var maybeString: String? = "Hello"
maybeString.length

Here we have the string and then question mark. If there’s a question mark, it’s nullable. Otherwise, you’re safe; you don’t have to worry about it. It’s similar to Swift’s optional if you’re familiar with that.

If you don’t check a nullable value for null before you call something on it, it won’t compile.

This uses the Safe Call Operator. That’s the question mark after maybeString and before .length.

val maybeString: String? = “Hello"
maybeString?.length

This calls .length on the object if it’s not null and returns null otherwise.

Here is the result in Java.

String maybeString = "Hello";
maybeString.length();

But, there are no null checks. Let’s see what happened if that variable is null. Will we get that null pointer exception?

If we set maybeString to null, let’s see what we get.

String maybeString = this.getString();
if(maybeString != null) {
 maybeString.length();
}

It is adding that null check in there. The compiler just knew that it couldn’t possibly be null. We were assigning a string to an immutable value because we used val, so it removed all the extra code for us.

By using the double bang or double exclamation point, you can get around compiler errors.

val maybeString: String? = getString()
maybeString!!.length

Having those two exclamation points after maybeString before you call length, it will call that method on that variable and not make you do the checks. Here it is in Java:

String maybeString = this.getString();
if(maybeString == null) {
 Intrinsics.throwNpe();
}
maybeString.length();

This will result in the NullPointerException.

Kotlin has a couple other null safety options. We can combine the Null Safety Operator with let to create null safe scoping.

val maybeString: String? = getString()
return maybeString?.let { string ->
 string.length
}

Here, we named a variable string within the block. If maybeString is not null, it will execute everything in the block. Otherwise, it will just return null. If we did not specify that inside the block we wanted it to be called string, it would default to a variable named it.

It’s a bit unimpressive for this small little code chunk, but it can be super handy for multiple lines of code. You can almost think of it as a map but for a single value. Here it is in Java:

String maybeString = (String) null;
if(maybeString != null) {
	String string = (String)maybeString;
	string.length();
}

It checks for null, assigns the value to a variable named string, and then performs any operation that you include in the lambda. Had we allowed it to default, that variable name would be it instead of string.

One last null check thing that we’ll look at today is the Elvis Operator. If you turned your head on your side, you might see a familiar face in it.

val maybeString: String? = getString()
return maybeString?.length ?: 0

With this, we can give an optional value if a variable is null. It’s much like an is not null ternary operator. This will return the length of a string if maybeString is not null, and zero otherwise.

String maybeString = this.getString();
return maybeString != null ?
 maybeString.length() : 0;

Delegation

Null safety and data classes are two of the biggest wins for me with Kotlin; but there are other things I enjoy.

Delegation can be a good replacement for inheritance. By using composition over inheritance, we can get out of some of the sticky situations that we get into with inheritance. Delegation is a form of composition. With it, we can cut down on some of our cognitive overhead and the number of things to keep track of when we’re reading and following code around.

Here’s the copy printer example in Kotlin.

class CopyPrinter(copier: Copy, printer: Print)
 : Copy by copier, Print by printer
interface Copy {
 fun copy(page: Page): Page
}
interface Print {
 fun print(page: Page)
}

The most interesting part of this copy printer is the class declaration. It says it copies by copy and prints by print, right there. Afterwards, we just have our interfaces for copy and print. It’s really clear and pretty simple. Here’s the Java.

public final class CopyPrinter implements Copy, Print {
 // $FF: synthetic field
 private final Copy $$delegate_0;
 // $FF: synthetic field
 private final Print $$delegate_1;
 public CopyPrinter(@NotNull Copy copier, @NotNull Print printer) {
 	Intrinsics.checkParameterIsNotNull(copier, "copier");
 	Intrinsics.checkParameterIsNotNull(printer, "printer");
 	super();
 	this.$$delegate_0 = copier;
 	this.$$delegate_1 = printer;
 }
 @NotNull
 public Page copy(@NotNull Page page) {
 	Intrinsics.checkParameterIsNotNull(page, "page");
 	return this.$$delegate_0.copy(page);
 }
 public void print(@NotNull Page page) {
 	Intrinsics.checkParameterIsNotNull(page, "page");
 	this.$$delegate_1.print(page);
 }
}
public interface Copy {
 @NotNull
 Page copy(@NotNull Page var1);
}
public interface Print {
 void print(@NotNull Page var1);
}

It is many more lines of code, but we can pretty clearly see what’s going on. There is a class, and it implements copy and print. Then we have fields to store the copy and print objects.

It then takes a copy and print in the constructor and assigns them to those fields. From there, it forwards the copy and print methods to those copy and print objects that were passed in in the constructor.

Finally, there are interfaces for copy and print, which look as you might expect.

Static Utility Classes

Some of the methods that we put in these can be application specific. Others are pretty consistent and almost feel like they should just be on the class themselves.

Kotlin has a pretty smooth way of handling this with extensions. It allows us to access these methods from an instance of a class. From Kotlin, it up looks like we’re modifying that class, even if it’s a final class like String.

TextUtils.isEmpty("hello");

One place that I found it helpful in the past is when I was doing a lot of math with time. With this, I added an extension function to date time, and I could easily and quickly set boundaries around a time.

Let’s look at an example of extending the string class. Remember that String is final, which means it can’t be inherited. We’ll just add a function that doubles the string, meaning it takes a string and puts it next to each other. This is what it looks like in Kotlin.

// StringExt.kt
fun String.double(): String() {
	return this + this
}

It really looks like we’re modifying the class. To declare it, we take the class name and then dot it with the function name. Here we’re doing String.double.

Then when we call it, it looks like we’re calling a method that has just always been on String.

"hello".double()

This may not be unexpected from an interpreted language, but it’s a lot different from what we’re used to in Java. Here’s the Java that comes from it.

public final class StringExtKt {
  @NotNull
  public static final String double(
    @NotNull String $receiver) {
      Intrinsics
        .checkParameterIsNotNull($receiver, "$receiver");
      return $receiver + $reveiver;
    }

This does what we manually do with a util class (making it a Final class). When you call it, it just calls that static method on the class. In fact, this is how we would call it if we had written our extension in Kotlin and then called it from Java in another part of our code.

StringExtKt.double("hello");

Functional Language Properties

Let’s look at an example. Let’s take creating a list of the first N squares. In Java, you might have a counter and a loop and add the square to the list and end the loop whenever you reach N. How might this look in Kotlin?

fun firstNSquares(n: Int): Array<Int>
  = Array(n, { i -> i * i })

Here, the lambda is passed into the array list constructor - this is in the closure. In it is an input variable, i, and then the operation we want to perform, i times i.

It looped through the numbers, zero through n, performs that operation, and adds it to the list.

What this looks like in Java:

@NotNull
public static final Integer[] firstNSquares(int n) {
 	Integer[] result$iv = new Integer[n];
 	int i$iv = 0;
 	int var3 = n - 1;
 	if(i$iv <= var3) {
 		while(true) {
 			Integer var9 = Integer.valueOf(i$iv * i$iv);
 			result$iv[i$iv] = var9;
 			if(i$iv == var3) {
 				break;
 			}
 			++i$iv;
 		}
 	}
 	return (Integer[])((Object[])result$iv);
}

Here is the same code with some of the variables renamed, and casting removed so we can understand it better.

@NotNull
public static Integer[] firstNSquares(int n) {
 Integer[] resultArray = new Integer[n];
 int i = 0;
 int max = n - 1;
 if(i <= max) {
   while(true) {
 	Integer square = i * i;
 	resultArray[i] = square;
 	if(i == max) {
 	  break;
 	}
 	++i;
   }
 }
 return resultArray;
}

It’s creating the list, looping through all of the numbers, performing the operation, adding it to the list, and then breaking when it reaches N. It’s the same concept that we might do if we were writing it in Java. We might use a different loop or put conditionals in a different place, but it’s the same idea.

We could easily also include a function in that lambda.

fun firstNSquares(n: Int): Array<Int>
  = Array(n, { i -> square(i + 1) })

Here, we square with i + 1, correctly making a list of the first N squares. The only thing here that changed is that method inside the lambda. In the Java example, the only line that changed, is the line to call that method.

Integer square = square(i+1);

The examples of functions that we’ve looked at so far have all been inlined. Here’s the let declaration, and you can notice the inline keyword in the signature.

public inline fun <T, R> T.let(block: (T) -> R): R
	= block(this)

This means that the compiler generates the code to insert into the body of the function where it’s being used.

inline fun beforeAndAfter(
 startString: String,
 function: (string: String) -> String
) {
 print("Before: $startString")
 val after = function(startString)
 print("After: $after")
}

Here’s the Java.

public final void beforeAndAfter(
 	@NotNull String startString,
 	@NotNull Function1 function) {
 Intrinsics
 	.checkParameterIsNotNull(startString, "startString");
 Intrinsics
 	.checkParameterIsNotNull(function, "function");
 String after = "Before: " + startString;
 System.out.print(after);
 after = (String)function.invoke(startString);
 String var4 = "After: " + after;
 System.out.print(var4);
}

We see that the function takes a string and Function1 as parameters. Here’s the interface for Function1.

public interface Function1<in P1, out R> : Function<R> {
 	public operator fun invoke(p1: P1): R
 }

The one corresponds to the number of parameters. If we had had two parameters, it would’ve been Function2. It has one method, invoke. The first thing inside of the body is those null checks. We’ve seen those before. Then we’re concatenating the string to print out.

Next is the interesting part.

public final void beforeAndAfter(
 	@NotNull String startString,
 	@NotNull Function1 function) {
 Intrinsics
 	.checkParameterIsNotNull(startString, "startString");
 Intrinsics
 	.checkParameterIsNotNull(function, "function");
 String after = "Before: " + startString;
 System.out.print(after);
 after = (String)function.invoke(startString);
 String var4 = "After: " + after;
 System.out.print(var4);

We’re calling invoke on the function that was passed in. That’s how it performs the lambda passed into the function. After that, we concatenate the results string and print that out.

fun example() {
 beforeAndAfter("hello", { string -> string + " world" })
}

Passing in the string part is really easy. The lambda passed in is contained in curly braces. Then the arrow indicates the operation we want to perform. In Java:

public final void example() {
 String startString$iv = "hello";
 String after$iv = "Before: " + startString$iv;
 System.out.print(after$iv);
 String string = (String)startString$iv;
 after$iv = (String)(string + " world");
 string = "After: " + after$iv;
 System.out.print(string);
}

Here are a couple of other ways we could call the before and after function.

beforeAndAfter("hello", { string -> string + " world" })
beforeAndAfter("hello", { it + " world" })
beforeAndAfter("hello") { it + " world" }

The first line is what we’ve seen before. If we don’t name the variable, it will default to “it”. which is shown in the second line. If a lambda is the last parameter of a function, you can put it outside of the parentheses.

Let’s see what it looks like if we do not have the function inlined.

fun beforeAndAfter(
 startString: String,
 function: (string: String) -> String
) {
 print("Before: $startString")
 val after = function(startString)
 print("After: $after")
}

Let’s decompile this and see what we get.

public final void example() {
 this.beforeAndAfter("hello", (Function1)null.INSTANCE);
}

We have null.INSTANCE. What is that? Upon inspection of the byte code we get this:

And in pseudocode:

// Pseudocode for bytecode
final class BytecodeClass
 extends Lambda
 implements Function1 {
 public void invoke(String string) {
 	StringBuilder sb = new StringBuilder("hello");
 	sb.append(" world");
 	returnValue = sb.toString();
 }
 static Function1 INSTANCE = new BytecodeClass();
}

It’s creating a class that extends the lambda and implements Function1. Then, as we might expect, it implements the invoke method. It creates a string builder and concatenates the given string with the string World. It then stores the result wherever it puts returned values.

Conclusion

We’ve looked at data classes, null safety, delegation, class extensions, and lambdas. Some additional language features to note are: - Companion objects and smart casting - Collection functions - Control flow structures - Operator overloading.

Android Studio offers an easy way to convert Java into Kotlin. To do this, go to the Java file that you want to convert, and go to the menu>Code>Convert Java File to Kotlin File.

After spending quite a bit of time with Kotlin and then working with Java, Java feels like a long construction detour that I was not expecting. I can still get to where I’m going, but it takes a lot longer and it’s much more annoying.

Interestingly enough, I’ve noticed that Kotlin has made me a better programmer in general.

You can find me on Twitter, @TTGonda.

Questions

When Apple released Swift 3, there was a lot of changes that caused issues for developers who were using Swift 2 in doing the migration. Do you think it’s going to be the same in Kotlin if we migrate to the next versions of Kotlin?

I know that they’re very focused on making the different versions of Kotlin as backward compatible as possible. I think we’ll have much less of an issue with that.

Have you faced any problems while working on Kotlin and trying to integrate libraries?

I’ve had almost no problems with this. Some libraries are starting to have Kotlin counterparts. In others, because it is completely interoperable with Java, there hasn’t been too much issue with that at all.

Do you have any tips for moving from writing a lot of Java code to writing a bit less Kotlin code after having written a lot of Java code in the past?

One thing is taking the Java code and converting it into Kotlin. That has been really helpful. We have a project that was entirely written in Java, and we’re slowly converting some of the files. Any new files that we make, we write in Kotlin, and because it’s so seamlessly interoperable, this is super easy to do.

Have you found that Kotlin is more for data classes and models and stuff like that, or do you do a lot of Android stuff with it also?

I just started working on an Android app that’s 100% Kotlin. It works for data classes, and everything else.

If you had to a put a number on it, how much faster do you think it’s been for you to write in Kotlin as opposed to Java?

I probably write at least a third faster.

Have you found that the final by default class structure from Kotlin has given you any issues?

Not too much. There’s some libraries, but it depends on them being extendable.

How is debugging in Kotlin?

It’s been pretty much just as easy as debugging in Java.


Victoria Gonda

Victoria Gonda

Victoria is a software developer at Collective Idea, building mobile and web applications. She is passionate about using technology to help better the lives of individuals. Studying both Computer Science and Dance in college, she now enjoys digging into dance technology, and keeping up with dance classes in her spare time.

Transcribed by Joseph Buelow