4. Kotlin and the Java Ecosystem

In this chapter we are going to investigate how Kotlin and Java interoperate. Generally, those two languages work very well together. However, there are some differences. For example, in Java we have beans versus properties in Kotlin. Kotlin has no static methods, instead we have the idea of companion objects. We have also seen that Kotlin has no checked exceptions and Java has. Suppose we write a Kotlin method that throws an exception, in Java we must make sure we catch that exception. Kotlin also gives us null safety, it will do it’s best not to throw NullPointerException and tries to it’s best not to allow you use null values in the code but if you are using nulls, it will at least make sure that the code is explicit to the use of nulls.

1. Using Java in Kotlin

Hands on the lab. Let create a Java class Person with some properties in it and see how we can work with Person objects in Kotlin.

Person.java:

public class Person {
private String name;
private int age;
private Person partner;

//getters and setters
...
}

Now, let’s create a main method in Kotlin:

Program.kt:

class Program {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val person = Person()

person.name = "Ivo"
person.age = 24

println("${person.name} is ${person.age} years old")
}
}
}

A couple of things to notice:

  • there are no “;” in Kotlin at the end of each file
  • we create an object in Kotlin style – no need for “new” keyword
  • even though we have created get/set methods in the Java class, we are still allowed to call the member variable as a property, instead of calling it’s get/set
Exceptions

What about exceptions? Let’s change the setter for age to throw an exception and see how Kotlin handles it.

Person.java:

public void setAge(int age) {
if(age<0) throw new IllegalArgumentException("age");
this.age = age;
}

Kotlin also has a try/catch blocks so the code will be very similar to what we would do in Java.

Program.kt

class Program {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val person = Person()

person.name = "Ivo"
person.age = 24

println("${person.name} is ${person.age} years old")

try {
person.age=-10
} catch(e: Exception) {
println(e.message)
}
}
}
}
Nulls

Next, let’s play around with null values. Person has a partner that currently is not set and it’s null. So let’s do the following:

class Program {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val person = Person()

person.name = "Ivo"
person.age = 24

println("${person.name} is ${person.age} years old")

val partner = person.partner
println(partner.name)
}
}
}

If this was pure Kotlin, it would be aware that person.partner could be a null. However, because partner is set in java, this would not be the case and it won’t put it’s usual null checks in. If we run that code we will get our most beloved NullPointerException. Again, this is because Kotlin is not yet aware that partner is null. Therefore, we need to help it a bit by going back to the Person Java class and annotate the getPartner() method with @Nullable. Once we do that the Kotlin code will no longer compile because now Kotlin is aware of the treat and tells us we must do something about it. And what we will do is simply use the elvis operator to put off the fire:

println(parthner?.name)

This line will now print for us “null”, instead of throwing NPE.

Inheritance

Finally, let’s raise the question, we have a class Person which is written in Java, is it possible to write a Kotlin class that extends that Java class? – The answer is yes, we can! We do that as if Person was just another Kotlin class:

class Student: Person() {}

The only thing to note here is that Person has a default constructor so we need to address that constructor in our extension expression by adding the “()” after “Person”.

2. Using Kotlin in Java

Creating objects from classes in Kotlin is pretty straightforward. Simply use the “new” keyword, followed by the defined constructor. Just the normal stuff.

Properties

Properties are a bit more interesting because in Kotlin we have val and var. If you have a var property, Kotlin will generate a get/set methods for that property. Then in Java you can call getProperty() and setProperty() to read/write that property. But if you have a val property, this would mean the property is immutable, Kotlin will generate only a getProperty() method and in Java you won’t have the option to call setProperty(), only getProperty(). In case, you want to expose that property directly as a field and not as bean, you can add an annotation in the Kotlin code that will do just that – @JvmField. This will tell Kotlin, instead of generating get/set methods, make the property public.

Static

We have already talked about static in Kotlin and that we use companion object to make a property or method, essentially static. So let’s say we want to define a constant in Kotlin that stores app version and access it in Java.

class KotlinConstant() {
companion object {
val APP_VERSION = 1
}
}

To access the APP_VERSION in Java though, we will have to go through that companion object and call it’s getAPP_VERSION() method, APP_VERSION is a regular val to the compiler after all so it will generate a get method. This is a big ugly to the eye, I would say. See for yourselves:

KotlinConstant.Companion.getAPP_VERSION()

What we actually want to achieve is a nice and readable KotlinConstant.APP_VERSION. We can achieve that, again, by using the annotation – @JvmField. The same applies for functions if you don’t annotate the method inside the companion object with @JvmStatic, you will have to call by first getting the Companion.

Exceptions

Let create our own GarageException and a function that throws that exception in Kotlin.

class Garage() {

fun addCar(val model: String) {
if(model.isNullOrEmpty()) throw GarageException("Car must have a model")
}

}

class GarageException(message: String) : Exception(message) {}

Now, if we go to Java and try to pass a null:

public static void main(String[] args) {
Garage garage = new Garage();
garage.addCar(null);
}

If you are using IntelliJ, you will already see a warning that the parameter is @NotNull, this information is coming from Kotlin, who is adding it to the compiled class. Let’s run the code either way. We get an exception but it’s not the exception that we created but it’s an IlligalArgumentException. This is still Kotlin, telling us “no, you cannot pass null to a non-null parameter”. So we need to fix this first. Let’s replace the null, we are passing as parameter, with and empty string. Now, we get the exception we expect – GarageException. Out next step will be to try to catch that exception.

public static void main(String[] args) {
Garage garage = new Garage();

try {
garage.addCar(null);
} catch (GarageException ex) {
System.out.println(ex.getMessage());
}
}

Now, we are getting a compiler error – GarageException is never thrown in the corresponding try block. The problem is that Java doesn’t know the exception is being thrown. In Java we add “throws” clause to our methods that may throw an exception to make Java aware of it. Kotlin doesn’t have a throws clause so the Java compiler doesn’t know that GarageException may come out from this method. To fix that we can add another annotation – @Throws, and we pass it the type of exception that we might throw as a parameter.

@Throws(GarageException::class)
fun addCar(val model: String) {
if(model.isNullOrEmpty()) throw GarageException("Car must have a model")
}

The compile error is gone from our Java code and sure enough if we remove the try/catch, the compiler will tell us there is an unhandled exception.

 

<<3. Functional Programming                                                 5. Testing with Spek >>