1.3 Kotlin Overview

If you've never programmed in Kotlin before, you might feel intimidated. Don't worry, it's a fairly intuitive language that simplifies the coding experience while providing powerful tools that ease the development of complex tasks. We'll provide a quick rundown of how it operates, but if you'd like something more in-depth, the Kotlin team has built a great tutorial at https://play.kotlinlang.org/koans/overview.

Variables

Kotlin is a statically-typed language, meaning that the type of the variable is set when being initialized. However, unlike the versions of Java you were most likely taught, Kotlin prefers to infer the type for you. For example, when initializing a variable named x to the integer value of 3, you can simply declare var x = 3.

Kotlin has now inferred that variable to be of type int, and as long as this variable is in the scope of the program, the type of x cannot be changed. Setting x = "hello" will lead to a compiler error. However, there are cases when Kotlin does not have enough information to infer the variable type. You can explicitly set the type with var x : String = "hello"

Java may have gotten you into the habit of primitives and objects. You can ignore such details when using Kotlin. It doesn't have primitives on the user side, only objects.

Mutability

There are two ways that you can declare a variable: val and var. val is used when the variable should not change through the duration of your code, and by default, you should try to use val whenever you can. If you do try to change the immutable variable, the compile will throw an error and you'll know at compile time. var is used for variables that are allowed to be mutable.

Nullability

What makes Kotlin easier to work with compared to Java is that null checking happens during compile time instead of runtime. By default, variables in Kotlin are not nullable, and setting a variable to null will yield an error. To make a variable nullable, you'll need to add a ? to the variable declaration like so var x? or var x: String?

Objects

class Cat(val name: String, var age: Int) {
    constructor(name: String) : this(name, 0)
    constructor(yearOfBirth: Int, name: String) : this(name, 2022 - yearOfBirth)
}

var a = Cat("Okayu", 18)

If that last example didn’t make any sense, don't worry -- we've got a quick overview of Kotlin's object system. An object type is always defined by a class and allows us to create instances of that class. For example the class Cat we defined above allowed us to create the Cat object a. The standard class file has three main things:

Instance variables

These are properties that are unique to each instance of an object. The instance variable type is shared among all objects but the value is different. For example, every cat has an age and a name, yet the values are different from cat to cat.

Constructor

The constructor is a special method that allows a programmer to create new instances of that class. Constructors typically update the instance variables of an object with specific values that are passed in, and are called as in the above example with:

<ObjectType> o = <ObjectType>(<Constructor arguments>)

If no constructor is defined for a class, Kotlin will create a default constructor that can take all the arguments detailed in the definition.

Methods

These are easily-repeatable chunks of code that typically perform some action using an object’s instance variables. Methods take arguments as inputs and can return a value as an output. Because Kotlin is statically-typed, we must define the types of the arguments and the return type when we create our method. Two methods are given for the Cat class below:

class Cat(val name: String, var age: Int) {
  val isNewborn
    get() = age == 0
    
  fun getBirthYear() : Int {
    return 2022 - age // close enough
  }
}

The first method is an getter created for the value isNewborn, which will return a true or false depending on the age. The second method roughly returns the birth year of the cat. Now, we can execute these methods on our cat a defined above.

if (!a.isNewborn) {
    val birthYear = a.getBirthYear() // birthYear = 2000
}

Getters and Setters

Kotlin automatically creates getter and setter methods for instance variables inside of data classes. Look at the code below to see how to use these methods:

data class Cat (
    val name: String,
    val birthYear: Int
)

val newCat = Cat("Felix", 2000)

// getter
val x = newCat.name // x = "Felix"

// setter
newCat.name = "Tammy" 
val y = newCat.name // y = "Tammy"

You can also override these methods:

var isNewBorn: Boolean
    get() = age == 0
    set(value) { value < 2022 }

Finally, you can also make getter and setter methods to private so that the method can only be used inside of its own class. This is a good way to protect the value of variables from being accidentally changed.

var isNewBorn: Boolean
    private set

Modifiers

Modifiers place restrictions on variables, methods, and classes and exist before the static type, return type, or word ‘class’, respectively. Some common modifiers include:

public/private/protected/internal modifiers

These define the accessibility of a variable, method, or class in Kotlin. Something declared public can be accessed from anywhere within your project, something private can only be accessed from within the same class, something internal is within the same class and all it's subclasses, and something protected (i.e. method, class, variable) can be accessed from any file within the same package.

Inheritance

Kotlin is amazing because basically every class represents its own unique Object type -- forcing programmers into a coding style of separating functionality into different files. Kotlin also has a powerful system of inheritance, allowing programmers to reuse code in the most efficient way possible. This becomes especially prevalent in Android development, as many objects in the Android class system share a lot of functionality.

Subclasses

This technique allows us to further break down our classes into increasingly more specific types. For example, we could create a class representing a Sphynx, and give it access to everything in Cat without explicitly copying the code. By default, classes in Kotlin cannot be inherited from, to do you you must explicitly state that said class is open.

open class Cat(val name: String, var age: Int) {
    ...
}

class Sphynx(name: String, age: Int) : Cat(name, age)

Now we could create a Sphynx object, and use all the methods and access the same instance variables from before. The super method call in the Sphynx constructor above calls the Cat constructor with arguments name and age, thereby setting the instance variables.

val b = Sphynx("Riza", 3022)
val birthYear = b.getBirthYear() // b = -1000

We can also create b by using Cat as the static type of b because this Sphynx is still a Cat object (we told Java this with the extends keyword).

Method Overriding

Now, we can override certain methods of our parent Cat class in Sphynx. Overridden methods must have the exact same method signature as the parent method, with the same method name, return type, and argument types. Here is the overridden getName() method:

class Sphynx(name: String, age: Int) : Cat(name, age) {
  override fun getBirthYear() : Int {
    throw Exception("(^_^)")
  }
}

Now, calling b.getBirthYear() will throw a funny error.

Interfaces

Interfaces are special classes used for organizational purpose that cannot be instantiated. A class implements an interface, which forces the programmer to provide a method body for all the methods defined in the interface.

There are a few other Kotlin things you ought to be familiar with (initialization of arrays, conditionals, arithmetic operations, etc.), so we recommend you take a look at Kotlin Koans by the Kotlin team themselves! Otherwise, a solid foundation of Java will get you most of the way through understanding the concepts in Kotlin. With that being said, as with most things, practice makes perfect.

Last updated