A study note based on Kotlin Class Documnet.

1 Classes and Inheritance

1.1 Constructors

The optional primary constructor is part of the class header. The constructor keyword is optional if it doesn’t have any annotations or visibility modifiers. The primary constructor cannot contain any code. Initialization code must be in initializer blocks which are prefixed with the init keyword. All initializer blocks are parts of the primary constructor.

You can have multiple init blocks that are executed in the order of positions. Initializer blocks are parts of the primary constructor.

Properties can be defined inside the primary constructor using val or var.

Constructors inside the class body are secondary constructors. They must delegate to the primary constructor using this keyword if there is one.

If no primary and secondary constructors defined, a default public primary constructor with no arguments will be generated.

To create an instance of a class, call the constructor as if it were a regular function.

Class can contain the following members:

  • Constructors and initializer blocks
  • Functions
  • Properties
  • Nested and Inner Classes
  • Object Declarations

1.2 Inheritance

All classes have a common superclass Any that is the default superclass for a class with no supertype declared. Any has three methods: equals(), hashCode() and toString().

By default, classes are final: they can’t be inherited. To make a class inheritable, mark it with the open keyword.

To declare an explicit supertype, place the type after :. There is a space before and after the :. If the derived class has no primary constructor, then each secondary constructor has to initialize the base type using the super keyword or to delegate to another constructor which does that.

To override a function or a property, use explicit modifier open in the supertype and override in the subtype. You should avoid using open members in the constructors, perperty initializer, and init blocks.

Inside an inner class, use super@Outer to access superclass of the outerclass. If a class inherits multiple implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones). Use super<ClassTypeName> to access supertype.

An abstract class is open and can have abstract open members.

1.3 Properties

The syntax for a property is

var | val <propertyName>[: <PropertyType>] [= <property_initializer>]
    [get() = ...]
    [set(value) {...}]

You can only define a field as a backing field of a property.

Use const val to define a read-only compile-time constant. It must be at the top level or a member of an object declaration or a companion object. It must be a value of type String or a primitive type. Compile-time constances can be used in annotations.

For a non-null property or local variable that can not be set in constructor, use lateinit keyword for a late-initilized property.

Kotlin supports delegated properties when you need a lazy property, an observalbe property or properties in a map. The syntax is var prop: Type by Delegate(). Kotlin provides several standard delegates including lazy and Delegates.observable().

1.4 Abstract Method (SAM) Interface Conversion

A SAM interface is also called a functional interface. It can have serveral non-abstract members but only one abstract member. Kotlin SAM conversion allows you to use a lambda expression block after the interface name to define an instance of a functional interface. Use fun interface key workd to define a SAM interface.

fun interface IntPredicate {
   fun accept(i: Int): Boolean

// create an instance of the interface using object expression
val isEven = object : IntPredicate {
   override fun accept(i: Int): Boolean {
       return i % 2 == 0

// SAM conversion converts a lambda expression to
// an instance of the interface
val isEven2 = IntPredicate { it % 2 == 0 }

1.5 Visibility Modifiers

Classes, objects, interfaces, constructors, functions, properties and setters can have visibility modifiers. In all places, public is the default: it is visible everywhere.

At package level:

  • private: inside the file
  • internal: same module – a source set in a Gradle project.

At class and interface level:

  • private: inside class
  • protected: inside and subclass
  • internal: inside the module

2 Extensions

Kotlin uses an extension to add functions or properties to a class. To declare an extension function, you need to prefix its name with a receiver type. The this keyword represents an receiver object. Extension functions are resolved/dispatched statically, i.e., they are not virtual by receiver type. They are determined by the declared type. For example:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp

Extension functions are dispatched statically, i.e. they are called based on the type of the expression on which the function is invoked.

Members have higher priority than extensions. Extension properties can only be defined by explicit getters/setters, no initializer allowed.

You can use ClassName.Companion prefix to define functions and properties for the companion object.

Extensions can be defined at top level or inside a class. You can import it without the extension receiver part.

If defined inside a class, the instance of the class in which the extension is declared is called the dispatch receiver, and the instance of the receiver type of the extension method is called extension receiver. In case there is a name conflict between the member of the two receivers, the extension receiver takes precedence. Use qualfied this@ClassName syntax to access a member of the dispatch receiver.

3 Data Classes

The compiler derives the following members for a data class if they are not defined or inherited from a supertype:

  • equals()/hashCode() pair
  • toString()
  • componentN() one function for each property in the declaration order
  • copy()

A data class has to follow rules:

  • the primary constructor needs to have at least one parameter
  • all primary constructor parameters need to be val or var
  • data classes cannot be abstract, open, sealed or inner

On the JVM, if the generated class needs to have a parameterless constructor, default values for all properties have to be specified.

Peroperties are excluded from the generated implementations of computing toString(), equals() and hashCode().

Component functions generated for data classes enable the destructuring declarations.

The standard library provides Pair and Triple data classes, but named data classes are better because they have meaningful names.

3 Sealed Classes and Generics

3.1 Sealed Classes

Sealed classes are used for restricted class hierarchies. All subclasses must be defined in the same file. A sealed class is abstract. It implements the union type.

In a when expression, the else is not needed if all cases are covered.

3.2 Generics

The declaration-site variance annotate the type parameter with out and in modifier to make a covoriant producer (output) or contravariant consumer (input or parameter). Consumer in - contravariant, producer out - covariant. Producer-Extends, Consumer-Super(PECS). The modifier is provided at the type parameter declaraion site.

Type project is use-site variance. Whe a type is used in both out and in positions, there is no way to use declaration-site variance, i.e., defining modifier in type declaration. Then type project can be used in use-site variance. A type is often used in methods/functions.

The * project means in Nothing for contravaritn and out Any? for covavarant.

Upper counds: T: Comparable<T>T must be a subtype of Comparable<T>. Use where to specify multiple upper bounds.

4 Enum Class

You use enum class to implement type-safe enums. Each enum is an instance of the enum class, they can be initialized, can delcare anonymous classes with methods and can implement interfaces.

enum class Direction {
  N, S, W, E

enum class Color(val rgb: Int) {

enum class ProtocolState {
    overrid fun signal() = TALKING

    overrid fun signal() = WAITTING

  abstrace fun signal(): ProtocolState

enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
    PLUS {
        override fun apply(t: Int, u: Int): Int = t + u
    TIMES {
        override fun apply(t: Int, u: Int): Int = t * u

    override fun applyAsInt(t: Int, u: Int) = apply(t, u)

Enum classes have syntetic methods valueOf() values(). enumValues<T> and enumValueOf<T> can be used to access constants. Every constant has name and ordinal properties and implement Comparable based on the order of definition.

5 Objects

You use object expression and object declaration to create an object of a slight modification of some classes.

5.1 Object Expression

Object expression has the syntax of object : SuperType {...} or simply object {...}.

open class A(x: Int) {
    public open val y: Int = x

interface B { /*...*/ }

// object expression creates a subclass of A and B
val ab: A = object : A(1), B {
    override val y = 15

5.2 Object Declaration

It has a syntax of object Object_Name {...}. It can have supert type. Object declaration are initialized lazily.

An object declared inside a class can be marked with the companion keyword. Companion object allows you to access an object’s member using only the class name as a qualifier. The companion object name is optional.

  • Use val x = MyClass.Companion to access the companion object
  • Use val x = MyClass to access the companion object
  • use MyClass.create to access the create method defined in companion object.

6 Type Aliase and Delegation

You can use typealias to provide alias for existing type, function types, inner and nested classes.

The delegation pattern is used to implement an interface by delegating all of its public members to a specified object.