Kotlin Classes

This page covers classes, creating instances, methods, constructors, and related features.

Classes and creating instances

Like Java, Kotlin is a class-based object-oriented language. In other words, you first create an instance from a class and then use it.

A class with no members is defined as follows. Like Java, Kotlin defines a class with the class keyword.

Example: Minimal class definition

class MyClass

Now instantiate this MyClass class.

Example: Creating an instance of a class

fun main() {
    val obj = MyClass()
    println(obj)
}

Output:

MyClass@5a07e868

MyClass() creates an instance of the MyClass class. In Java, you must explicitly use the new operator to create an instance, but in Kotlin you do not need to write it. In the example above, an instance of the MyClass class is created and its reference is assigned to the variable obj. When it is passed to println, the class name and hash code are printed.

Methods

Now define a class that has a method. The Greeter class in the example below has a greet method. Class members are wrapped in braces ({}), and methods are written like functions using the fun keyword.

Example: Class with a method

class Greeter {
  fun greet() {
    println("Hello!")
  }
}

Create an instance of the Greeter class and call the greet method on that instance.

Example: Calling a method

fun main() {
    val greeter = Greeter()
    greeter.greet()
}

Output:

Hello!

Since Kotlin uses the fun keyword when declaring methods, it may be more accurate to call them functions rather than methods. Their role and meaning are effectively the same, so this page uses the terms method and function interchangeably.

Properties

A class can have properties. A property can be explained simply as a field plus its accessors, such as a setter and getter, in Java terms.

The User class below has properties named id and name. Create an instance, set values on the properties, and read them.

Example: Class with properties

class User {
    var id: Long = 0
    var name: String = ""
}

In the third line of the next example, a value is set on the name property. In the fourth line, values are read from the name and id properties. It looks like direct field access in Java, but it actually goes through the generated setter or getter by default.

Example: Using properties

fun main() {
   val user = User()
   user.name = "devkuma"
   println("${user.name}(${user.id})")
}

Output:

devkuma(0)

setter / getter

If you want to customize a setter or getter, you can freely add processing with syntax similar to a function, as shown below.

Example: Custom setter and getter

class User {
    var id: Long = 0

    var name: String = ""
        // Prints a log when setting a value.
        set(value) {
            println("set: $value")
            field = value
        }
        // Prints a log when reading a value.
        get() {
            println("get")
            return field
        }
}

Output:

set: devkuma
get
devkuma(0)

For a class property, set() is the function that sets a value, and get() is the function that reads a value. Inside these functions, field represents the backing field value.

Constructors

constructor defines a constructor that is called automatically when a class instance is created.

Example: Creating a constructor

class User1 constructor(id: Long, name: String) {
    var name = name
}

fun main() {
    var user1 = User1(1,"devkuma")
    println(user1.name)
}

output:

devkuma

If you do not need to specify a visibility modifier, annotation, or similar item on the constructor, you can omit constructor. As in the example below, the constructor parameter list can be declared after the class name. Properties can be initialized with the received arguments.

Example: Omitting constructor

class User2(name: String) {
    var name = name
}

If the constructor only initializes properties, it can be shortened even further. By adding val or var in front of constructor parameter names, you can define properties and simplify the code.

Example: Defining properties with constructor parameters

class User3(var name: String)

The constructor above is called the primary constructor. You can also define secondary constructors as shown below. Multiple secondary constructors can be written depending on the number and types of parameters.

Example: Primary and secondary constructors

class User4 {
    var name: String
    var age: Int = -1

    constructor(name: String) {
        this.name = name
    }

    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

If a class has a primary constructor, secondary constructors must call the primary constructor using this.

class User5(var name: String, var age: Int) {
    constructor(name: String) : this(name, -1)
}

Initialization block

A class can define an initialization block with init {...}. The initialization block is called before the constructor body runs.

class Foo {
    init {
        println("Foo is created.")
    }
}

Class inheritance

As in Java and other object-oriented languages, a class can inherit from another class. When a class inherits, it becomes a subtype of the parent class, or superclass, and superclass members can be treated as members of the subclass.

Creating an inheritable class (open)

First, define an Animal class that will become the superclass.

Example: Defining a superclass

open class Animal {
    fun running() {
        println("running")
    }
}

Animal has no constructor parameters or properties and has a running method. The point to notice is that it has the open keyword. In Kotlin, a class must be explicitly marked with the open modifier to be inheritable. Conversely, a normal class without open cannot be inherited.

You can also explicitly add the final keyword to indicate that a class cannot be inherited, but it is usually omitted because that is the default.

final class Animal() { ... }

This may look like a strict restriction, but it follows the design philosophy of Item 17 in “Effective Java, 2nd Edition.”

Now define a Cat class that inherits from Animal. A subclass (Cat) that inherits from a superclass (Animal) can be defined as follows.

Example: Defining a child class

class Cat : Animal() {
    fun sound() {
        println("sound")
    }
}

After the class name, write a colon (:) and a call to the parent class constructor. Here, Animal has no constructor parameters, so it is written simply as Animal().

Now check whether a method defined in the superclass can be called from a Cat instance.

Example: Calling a superclass method

fun main() {
    val cat = Cat()
    cat.running()
    cat.sound()
}

Output:

running
sound

Since Cat is a subtype of Animal, val animal: Animal = cat is valid code.

val animal: Animal = cat
animal.sound() // Error

However, when written this way, subclass-specific members cannot be called through animal.

Overriding class functions

override lets you redefine a function from a parent class.

open class Foo {
    open fun print() {
        println("Foo")
    }
}

class Bar() : Foo() {
    override fun print() {
        println("Bar")
    }
}

fun main() {
    var bar = Bar()
    bar.print()
}

Output:

Bar

Calling the parent class constructor (super)

Use super() to call the parent class constructor.

open class Animal(var type: String)

class Cat: Animal {
    var name: String
    constructor(name: String): super("Cat") {
        this.name = name
    }
}

You can also write it as follows.

open class Animal(var type: String)
class Cat(var name: String): Animal("Cat") {}

Nested and inner classes

Classes can be nested.

class A {
    class B { ... }
}

var obj = A.B()

inner indicates that a nested class is an inner class.

class A {
    inner class B { ... }
}

var obj = A().B()

.toString()

Every class has a .toString() function for string conversion. It is called implicitly when outputting with println(), for example. You can override it as follows.

class Person(val name: String, var age: Int) {
    override fun toString() = "$name($age)"
}

fun main() {
    println(Person("devkuma", 26))  // devkuma(26)
}

.equals()

Every class has an .equals() method for comparison. It is called implicitly when comparing with ==, for example. You can override it as follows.

class Foo(val name: String) {
    override fun equals(other: Any?) = this.name == (other as Foo).name
}

fun main() {
    val a1 = Foo("devkuma")
    val a2 = Foo("devkuma")
    println(a1 == a2)        // true
}

References