Hero Image

From Java to Kotlin Part 4

I hope you found our closeup of parameters in the previous blog interesting. This time we're going to look at classes, the most basic building blocks. In object oriented software, classes are the coat rack we hang everything else on. Does Kotlin have the same class types at Java or totally different ones? Let's find out! ​

The usual suspects

Just like Java, Kotlin has regular classes, abstract classes, interfaces, enums and annotations though their syntax might differ slightly. ​

Java

public class MyClass
public interface MyInterface
public abstract class MyAbstractClass
public enum MyEnum
public @interface MyAnnotation

Kotlin

open class MyClass
interface MyInterface
abstract class MyAbstractClass
enum class MyEnum
annotation class MyAnnotation

Visibility and Finality

In Java, if you leave out the public keyword, the class has package-private visibility, meaning it can only be accessed from within the same package. In Kotlin the default visibility is public, so without any explicit modifier it can be accessed from anywhere. Kotlin does not have the package-private modifier, but it does have internal which means a class can only be accessed from inside the same module (a Maven module for instance). ​ Classes in Kotlin are final by default, meaning they can't be extended. To make a class non-final, you have to add the open keyword. Many libraries such as Spring and Hibernate require classes to be open. You could either add open yourself or use compiler plugins to automatically open these classes during compilation. ​

Records

In the previous article, I talked briefly about data classes, let's compare them to what Java offers. As of version 14, Java has added records which can be used as simple data holders:

record Person(String name, int age)

A record automatically gives you a constructor, private final fields with accessor methods, an equals and hashCode and a toString method. You may have used Lombok's @Value or @Data annotations for this. In Kotlin, we can achieve this with a data class.

data class Person(val name: String, val age: Int)

Besides the aforementioned methods, data classes also offer a copy function to easily obtain a new instance. As an added bonus, because of default and named parameters, you can create a copy with slightly different values, for instance myPerson.copy(name = "John") would create a copy of myPerson with the same age, but a different name. ​

Objects

Sometimes a single instance of an object is all that is needed. Unfortunately, Java does not have a very useful built-in way to get a singleton, the best you can do is something like this:

public class MySingleton {
    private MySingleton() {}
    private String state = "some state";
    private static MySingleton INSTANCE = new MySingleton();
}

This works, even though it has thread safety issues and therefore it is recommended to use an enum instead:

public enum MySingleton {
  INSTANCE("some state");
  private String state;
  private MySingleton(String state) { this.state = state; }
}

In Kotlin, we can use objects for this:

object MySingleton {
  private var state = "some state"
}

Objects are thread-safe and a nice way to implement the singleton pattern. ​

Sealing

As of version 17, Java introduced sealed classes, this lets you explicitly define all of the subclasses for a given base class to let the compiler know there can't be any other subclasses:

public sealed interface Animal permits Cat, Dog {}
public final class Cat implements Animal {}
public final class Dog implements Animal {}

Kotlin also offers this functionality

sealed interface Animal
class Cat(): Animal
class Dog(): Animal

Sealed classes are especially useful when having to do type checks because the compiler knows the check is exhaustive, such as in a switch / when construction. ​

Type aliases and inline classes

Sometimes you are working with types with weird signatures, such as when generics is involved. To make these types more readable, you can use type aliases:

typealias ParameterMap = Map<String, List<String>>

You can use the ParameterMap type name at any place where a Map<String, List<String>> is used, for instance as a parameter value. They are interchangeable. If you want more type safety than that, you can use inline classes. This can prevent the user from accidentally passing a value as the wrong parameter if it happens to have the same type.

// image we have the following function
fun login(username: String, password: String)
// it would be possible to accidentally call the function with reversed parameters like this
login("password", "username")
// we can create an inline class for one of the parameters
value class Password(private val s: String)
// and change our function to this
fun login(username: String, password: Password)
// now we have to call it like this
login("username", Password("password"))

It is important to note that no extra object is created in the heap for this wrapper type. In the heap, it's just a regular string. The inline class offers compile time safety without any runtime overhead. ​

Summary

That about wraps it up for classes, but there's a lot more to learn about them. If you're really curious, just head over to Kotlin's documentation on classes for some light reading. We've seen that Kotlin offers the same types of classes as Java with some extras. Kotlin had data classes and sealed classes for a long time and it's nice that Java is catching up. Objects offer a canonical way to implement the singleton pattern and type aliases and inline classes are nice quality of life features to make code more readable and safe. Until next time!