In my previous post I wrote about properties and null safety, but we learned how to define class just for understanding the example. It is appropriate time to know how OOP works in Kotlin. Let’s start with some prerequisites.

Kotlin file content

First of all, there is no connection between filename and content naming. A single file can consist of any quantity of classes OR top-level declarations (declarations without classes), so in a single file there may be no class at all – as a matter of fact, this can be often observed in kotlin source.

Kotlin data classes: tutorial

Visibility modifiers

Talking about classes requires a word about visibility modifiers, we have four modifiers to use:

  • public
  • internal
  • protected
  • private

The default one is public (visible everywhere), there is no package visibility as in Java. Kotlin introduces internal keyword, which tells everything marked with this modifier will be visible inside current module. But what exactly is this module? It depends on how you compile your code. It is either IDE module or project / task defined by your favourite buildtool.

Visibility modifiers may have different meaning for different context. A private member will be reachable only inside the class – this means an outer class doesn’t see private members of its inner class. Protected visibility works pretty much as private one with one extension – protected members of superclass are visible within subclasses, which is what I personally craved for in Java. Top-level declarations cannot be marked as protected, and private keyword limits the visibility to the file.

Classes

An example explains more than words, so let’s slightly modify the class familiar from Part 1.

The first noticeable thing is constructor keyword, not present before. That is because it is necessary only when some customization is needed (injection or visibility modification for instance). Constructors implemented this way are called primary constructors. The body of such constructors consists of the following things:

  • delegation to superclass constructor
  • initializer block
  • assignment values straight to properties from primary constructor parameters

I ommited var keyword for purpose – numberParam is just a parameter, not a property in that case. It can be used for property assignment or inside the initializer block, however it is not visible for any method.

The name primary constructor suggests that there might be a secondary constructor. That’s right, but there is a rule – secondary constructors must delegate primary constructor – via this keyword.

One thing I personally dislike is a possibility for not declaring any primary constructor. You may have only secondary constructors as well. In that case there is no need for delegating the primary constructor, simply because we have none.

Interfaces

Interfaces can have properties and methods. Both of them can be abstract or provide default implementation:

Declaring methods requires information about parameters/result names and types. Nothing new, just like in Java 8, but wait, where is the formatNumber type? In previous article a type inference mechanism was mentioned, and that is why compiler does not protest. Inferred type for formatNumber method is Unit – equivalent of void.

The rule for properties is that properties in interfaces cannot have backing field, because interfaces are not meant to bring the state. Feel free to provide accessors implementation, let them to be abstract otherwise.

Inheritance

Inheritance works pretty much as for Java – class can extend only one supertype, but it can implement multiple interfaces without any limitation. Each class extends Any, which is not Object, although Objects are translated to Any when importing Java source.

Inheritance can be achieved only using colon character, either for extending classes and implementing interfaces, but it’s possible to put paranthesis after the superclass name – which is basically constructor delegation. When overriding, you can change the visibility modifier, but only for widening. As an abstract class can inherit from non-abstract one, members may be overriden as abstract.

Background is covered, let’s go to some cool things.

Data Classes

This mechanism provides some useful features for classes which hold nothing but data.

Equality

First of all, equals and hashCode methods are generated, so they can be used safe with equality comparision. There is one difference from Java – operator === checks referential equality, while == is meant for structural equality (basically equals invocation) – when invoked, all properties from primary constructor are compared and based on this and the type we have the result. Let’s see an example:

toString

Default toString method is generated by following template Classname(property1=value1, property2=value2, …, propertyX=valueX)

copy

Let’s imagine we want to clone an instance of data class object with modifying some properties – copy function is something we get for free, what if we want to modify some properties? Kotlin comes with named arguments as a solution.

We can change just those values we are really interested in. Creating methods with named arguments will be presented in the next part, so be patient.

Destructuring declarations

The compiler generates componentN functions for data classes providing easy access for each property, a really handy mechanism for mutlideclarations.

This declaration is basically equivalent to:

We can use the same approach with for loops:

As data classes are recommended approach for tuple substitution (yes, tuples are not part of kotlin), Pair and Triple are standard data classes providing all mentioned functionalities. Also, componentN functions are combined with Map interface, giving opportunity to traverse a map with destructuring declarations.

Sealed classes

This is the answer for Algebraic Data Types [1] representation. They are abstract by default and may be subclassed only by nested classes and objects.

For evaluating sealed classes, when statement comes as a helpful tool. In general, it is just switch known from Java, but more powerful.

Matching against constant is not required, as in the last condition check. Returning to sealed class example:

You may have noticed one subtle thing – I accessed message property of result (and error for the second case), but Result sealed class does not have one. The answer is simple. Kotlin supports smart cast, and this is not only for sealed classes, but after every type check with is operator.

Summary

We’ve got another dose of what kotlin offers and how to use it, but it’s not the end, so stay tuned!

References:
  1. http://merrigrove.blogspot.com/2011/12/another-introduction-to-algebraic-data.html
Parts:

Android Developer

I am keen on functional programming and JVM technologies, right now diving deeply into Kotlin ocean. After work I wear my captoe shoes and rush to swing dance classes.