πŸ“š Kotlin language features

Kotlin is an object oriented language with functional features. This chapter covers important and relevant features of the language slit into basic and intermediate. Another chapter covers advanced features.

Basic features

Basic constructs (variables, control flow)

  • Kotlin is statically typed and supports implicit typing.
    • Static typing: types cannot change on runtime (it is the opposite of dynamic typing).
    • Implicit typing: the compiler can infer the type whenever possible.
  • var creates mutable variables.
  • val creates immutable variables or constants.
  • Semi-colons are optional.
  • Kotlin supports top level declaration of variables and functions (They can be declared outside of classes).
  • String interpolation is available with this syntax ${expression}.
  • if, and when statements are expressions (they can return a value).
    • when is equivalent to the switch statement of other languages
    • The ternary operator is not available but the if statement replaces-it.
  • for-each is the only type of for loop available.
  • Object oriented programming is supported as in Java with some additional features such as extensions.
  • The compiler supports Null Safety. It allows to write code free from null pointer errors with a compile time guarantee.
  • Functional programming is supported (Higher-order functions and functions as 1st class items, etc.).

Use val by default

Replace by var only if needed.

▢️ this codeopen in new window highlights the above features.

Functions

In the this section, the terms 'argument' and 'parameter' are used interchangeably.

Functions in Kotlin have the following features:

  • Declaration: fun functionName(arg1: type1 = defaultvalue1, ...) : retunrType.
  • Call a function by passing the value in the declaration order.
    • Use argument labels for more clarity, however, it also allows for arbitrary ordering of arguments.
  • Optional arguments have a default value and can be omitted during the call.
  • Functions are first class items or citizen: they can be assigned to a variable, passed as a function parameter, or returned from a function.
    • πŸ’‘ A function that takes a function as an argument or returns one is a higher order function.
  • A function type can be expressed as follows: (typeOfParam1, typeOfParam2, etc) -> returnType (The empty return type is Unit).
  • Anonymous functions use the following syntax { argName1, argName2, etc. -> // code }
    • Also called or lambda functions or literal functions
  • The last function argument can be put after the closing after the closing parenthesis compute(9, 5) { x, y -> x * y }

▢️ this codeopen in new window highlights the above features.

The next section talk about null safety.

Null safety

In a nutshell, null safety is a compiler feature that eliminates the infamous Null pointer exception or npe. The Kotlin compiler reports errors and warnings when we manipulate nullable (also called optional) values. Here is a list of null safety features provided by Kotlin:

  • All types are non-nullable by default; we cannot assign null to a variable or an argument.
    • For example, this code fails var s: String = null.
  • A type can be made nullable by suffixing it with a ?. For example: var s: String? = null.
  • Kotlin forbids calling a method or a property of a non-nullable type, unless we do one of these possibilities:
    • Use optional chaining with the ? suffix.
    • Provide a default value with the elvis ?: operator.
    • Smart-cast the nullable into a non-nullable.
    • Use the !! operator that eliminates compiler checks. This should never be used.

Never unwrap with !!

Use other safe techniques instead.

▢️ this codeopen in new window illustrates null safety and how to use optional types.

Java `Optional` does not provide compile time null checks

Optional wrap null values on runtime. The Java compiler (as of version 17) does not provide unwrapping features such as smart casting. It is still possible to have a npe like this: Optional<String> s = null; s.isPresent();

Enumerations

Enumerations allow to work with a group of values in a type-safe fashion. Unlike Java enums, Kotlin enums are classesopen in new window. Kotlin enum classes provide these features:

  • when statements support enumerations.
  • Enum constants can declare their own anonymous classes with their corresponding methods, as well as with overriding base methods.
  • An enum class can implement an interface but it cannot derive from a class
  • There are methods for listing the defined enum constants and getting an enum constant by its name.
  • Every enum constant has properties for obtaining its name and position (starting with 0).

▢️ this codeopen in new window illustrated the features above. For further reading please consult the official documentationopen in new window.

πŸ§ͺ Exercises

Exercise 1

Please click on this link to view the exerciseopen in new window

Please open to see the solution(s)

Solutionopen in new window

Exercise 2

Please click on this link to view the exerciseopen in new window

Please open to see the solution(s)

Solutionopen in new window

Intermediate features

Object oriented programming

Kotlin allows to write concise OOP code and has the following features:

  • Available common features: classes, inheritance, interfaces, and abstract classes.
  • Native support of properties: do not define getters and setters unless needed.
    • Just add get() and set(value) functions next to the property declaration.
  • Constructor arguments are defined next to the class name class ClassName(arg1, atg2, )
  • Prefixing a constructor arguments with val or var makes it a property (val makes it read-only).
  • The constructor name is init and does not require parameters.
  • The compiler checks that all non-nullable properties are initialized by the end of the constructor.
    • ⚠️ The compiler does not check the initialization of lateinit properties. Thus, accessing them before while uninitialized causes an exception.
  • Prefix classes with open to allow inheritance.
  • Kotlin enables the public access level by default.
  • The equality operator == calls equals() (as opposed to Java which uses reference equality).
  • A companion object contains static methods and properties.
  • Extensions add function and properties to existing classes.
    • πŸ’‘ They replace inheritance in many situations.
    • For example, we can add functions to the String class instead of creating a new StringUtils class.
  • Sealed classes and interfaces cannot be extended or implemented by third parties.

Do not define accessors unless needed

As opposed to Java, Kotlin supports properties and allows to add accessors later without refactoring the code that calls these properties. Thus, by default, just define the name of properties without accessors and use them directly.

▢️ this codeopen in new window illustrates some features.

Data class

Data classes are final (cannot be inherited from) classes that provide standard functionalityopen in new window:

  • equals() and hashCode()
  • toString() of the form "class(field=value, ...)"
  • componentN() that correspond to the properties in their order of declaration.
  • copy()

However, they have the following constraintsopen in new window:

  • The primary constructor needs to have at least one parameter.
  • All primary constructor parameters need to be marked as val or var.
  • They cannot be abstract, open, sealed, or inner (πŸ’‘ but extensions are possible).

▢️ this codeopen in new window illustrates some features.

Functional programming

General concepts

Functional programming revolves around these conceptsopen in new window: pure functions, recursion, referential transparency, immutable variables, functions as first-class citizens, and higher-order functions.

Let's briefly explain these concepts:

  • Immutable variables means that we cannot change the value of a variable or its properties once it has been created. If we want to do so, we must create a new instance with the new value.
  • Pure functions are functions that do not have side effects and will thus return always the same output given the same input.
  • Functions are first class citizens: they can be assigned to a variable or used in higher-order functions (passed as a function parameter to another function or returned from a function).
  • Referential transparencyopen in new window: means that an expression can be replaced by its result without changing the behavior of the program. Transparency refers to the fact that the implementation of the expression is irrelevant.

πŸ’‘ Pure functional languages provide these features natively and enforces them (at build time).

Kotlin and functional programming

Kotlin is not a pure functional languages but it supports some features. For example, Kotlin does not have compile time verification of pure functions, but it provides immutable collections through the kotlinx.collections.immutableopen in new window library.

listOf generates read-only lists, which are not immutable

A read-only listopen in new window cannot add or remove elements, but it can change the underlying data.

@Test
fun givenReadOnlyList_whenCastToMutableList_checkNewElementsAdded(){
    val list: List<String> = listOf("This", "Is", "Totally", "Immutable")
    (list as MutableList<String>)[2] = "Not"
    assertEquals(listOf("This", "Is", "Not", "Immutable"), list)
}

The Arrow-ktopen in new window library add more functional programming features.

Declarative programming

Declarative programming is a famous style within functional programming. It consists of writing code as a chaining of function calls in this style val result = f(x).g(y). .... Higher order functions replace many situation where we would use loops. This favors readable code which is easy to debug an maintain.

▢️ this codeopen in new window show an example of list manipulation using declarative programming.

Kotlin and Java interoperability

  • Kotlin is designed with Java interoperability in mind.
  • Kotlin code may require some annotations to be called from Java.
  • It is possible to mix Java and Kotlin in the same project.
  • JetBrain's IntelliJ and Android Studio can convert to Kotlin when pasting java code.
  • Kotlin generates Java records by annotating a data class with @JvmRecord and targeting JVM 16, among other requirement listed hereopen in new window.
  • It is much more easier and natural to call Java from Kotlin.
    • For example: Java accessors are converted to Kotlin properties.

▢️ this codeopen in new window shows how to convert a Kotlin List to a Java ArrayList.

The official documentation provides exhaustive documentation on Kotlin and JVM integrationopen in new window

πŸ§ͺ Exercises

Exercise 3

Please click on this link to view the exerciseopen in new window

Please open to see the solution(s)

Solutionopen in new window

Exercise 4

Please click on this link to view the exerciseopen in new window

Please open to see the solution(s)

Solutionopen in new window

Exercise 5

Please click on this link to view the exerciseopen in new window

Please open to see the solution(s)

Solutionopen in new window

Exercise 6

Please click on this link to view the exerciseopen in new window

Please open to see the solution(s)

Solutionopen in new window

Exercise 7

Please click on this link to view the exerciseopen in new window

Please open to see the solution(s)

Solutionopen in new window

πŸ“– Further reading