Scroll to top
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Advanced Functions
Kotlin From Scratch: Advanced Properties and Classes

Kotlin is a modern programming language that compiles to Java bytecode. It is free and open source, and it promises to make coding for Android even more fun.

In the previous article, you learned advanced uses of functions, such as extension functions, closures, higher-order functions, and inline functions in Kotlin.

In this post you'll get an introduction to object-oriented programming in Kotlin by learning about classes: constructors and properties, casting, and more advanced class features that Kotlin makes easy.

  1. Classes
  2. Any and Nothing Types
  3. Visibility Modifiers
  4. Smart Casting
  5. Explicit Casting
  6. Objects
  7. Companion Objects

1. Classes

A class is a program unit that groups together functions and data to perform some related tasks. We declare a class in Kotlin using the class keyword—similar to Java.

1
class Book

The preceding code is the simplest class declaration—we just created an empty class called Book. We can still instantiate this class even if it doesn't contain a body using its default constructor.

1
val book = Book()

As you can observe in the code above, we didn't use the new keyword to instantiate this class—as is usual in other programming languages. new is not a keyword in Kotlin. This makes our source code concise when creating a class instance. But be aware that instantiation of a Kotlin class in Java will require the new keyword.

1
// In a Java file

2
Book book = new Book()

Class Constructors and Properties

Let's look into how to add a constructor and properties to our class. But first, let's see a typical class in Java:

1
/* Java */
2
public class Book  {
3
    private String title;
4
    private Long isbn;
5
    public Book(String title, Long isbn) {
6
        this.title = title;
7
        this.isbn = isbn;
8
    }
9
    public String getTitle() {
10
        return title;
11
    }
12
    public void setTitle(String title) {
13
        this.title = title;
14
    }
15
    public Long getIsbn() {
16
        return isbn;
17
    }
18
    public void setIsbn(Long isbn) {
19
        this.isbn = isbn;
20
    }
21
}

Looking at our Book model class above, we have the following:

  • two fields: title and isbn
  • a single constructor
  • getters and setters for the two fields (fortunately, IntelliJ IDEA can help us generate these methods)

Now let's look at how we can write the preceding code in Kotlin instead:

1
/* Kotlin */
2
class Book {
3
    var title: String
4
    var isbn: Long
5
6
    constructor(title: String, isbn: Long) {
7
        this.title = title
8
        this.isbn = isbn
9
    }
10
}

A pretty tidy class! We've now reduced the number of lines of code from 20 to just 9. The constructor() function is called a secondary constructor in Kotlin. This constructor is equivalent to the Java constructor that we called when instantiating a class.

In Kotlin, there is no concept of a field as you might be familiar with; instead, it uses the concept of "properties". For example, we have two mutable (read-write) properties declared with the var keyword: title and isbn in the Book class. (If you need a refresher on variables in Kotlin, kindly visit the first post in this series: Variables, Basic Types, and Arrays).

An amazing thing is that the getters and setters for these properties are auto-generated for us under the hood by the Kotlin compiler. Notice that we didn't specify any visibility modifiers to these properties—so by default, they're public. In other words, they can be accessed from anywhere.

Let's look at another version of the same class in Kotlin:

1
class Book constructor(title: String, isbn: Long) {
2
    var title: String
3
    var isbn: Long
4
5
    init {
6
       this.title = title
7
       this.isbn = isbn
8
    }
9
}

In this code, we've removed the secondary constructor. Instead, we declared a constructor in the class header called a primary constructor. A primary constructor doesn't have any place to put a block of code, so we utilize the init modifier to initialize incoming parameters from the primary constructor. Note that the init code block is executed immediately when the class instance is created.

As you can see, our code still has a lot of boilerplate. Let's reduce it further:

1
class Book constructor(var title: String, var isbn: Long)

Our Book class is now just one line of code. That's really cool! Notice that in the primary constructor parameter list, we defined our mutable properties: title and isbn directly inside the primary constructor with the var keyword.

We can also add default values to any of the class properties right inside the constructor.

1
class Book constructor(var title: String = "default value", var isbn: Long)

In fact, we can also omit the constructor keyword, but only if it doesn't have any visibility modifier (public, private, or protected) or any annotations.

1
class Book (var title: String = "default value", var isbn: Long)

A very neat class, I must say!

We can now create a class instance like this:

1
val book = Book("A Song of Ice and Fire", 9780007477159)
2
val book2 = Book(1234) // uses the title property's default value

Accessing and Setting Properties

In Kotlin, we can get a property by the class object book, followed by a dot separator ., then the property name title. This concise style of accessing properties is called property access syntax. In other words, we don't have to call the property getter method to access or call the setter to set a property in Kotlin—as we do in Java.

1
println(book.title) // "A Song of Ice and Fire"

Because the isbn property is declared with the var keyword (read-write), we can also change the property value using the assignment operator =.

1
book.isbn = 1234
2
println(book.isbn) // 1234

Let's see another example:

1
class Book (
2
    var title: String, 
3
    val isbn: Long
4
)
5
6
val book = Book("A Song of Ice and Fire", 9780007477159)
7
book.isbn = 1234 // error: read-only property

8
book.title = "Things Fall Apart" // reassigned title with value

Here, we updated the isbn parameter to be immutable (read-only) instead—by using the val keyword. We instantiated a class instance book and reassigned the title property the value "Things Fall Apart". Notice that when we tried to reassign the isbn property value to 1234, the compiler complained. This is because the property is immutable, having been defined with the val keyword.

Java Interoperability

Be aware that by declaring a parameter with the var modifier inside the primary constructor, the Kotlin compiler (behind the scenes) has helped us to generate both the property accessors: getter and setter. If you use val, it will generate only the getter.

1
/* Kotlin */
2
class Book (
3
    var title: String, 
4
    val isbn: Long
5
)

This means that Java callers can simply get or set the property field by calling the setter or getter method of the property respectively. Remember, this depends on the modifier used to define the Kotlin property: var or val.

1
/* Java */
2
Book book = new Book("A Song of Ice and Fire", 9780385474542)
3
println(book.getTitle()) // "A Song of Ice and Fire"

4
book.setTitle("Things Fall Apart") // sets new value

5
println(book.getTitle()) // "Things Fall Apart"

6
7
book.getIsbn() // 9780385474542

8
book.setIsbn(4545454) // won't compile

Custom Getters and Setters

In this section, I'll show you how to create custom accessors (getters and setters) for a property in Kotlin if you want to. Creating a custom setter can be useful if you want to validate or verify a value before it's set to a class property. And a custom property getter can be useful when you want to change or modify the value that should be returned.

Creating a Custom Setter

Because we want to create our own custom getter or setter for a property, we have to define that property in the class body instead of the constructor header.

1
class Book (val isbn: Long) {
2
    var title = "default value"
3
}

This is why we moved the mutable (read-write) title property into the class body and gave it a default value (or else it wouldn't compile).

1
class Book (val isbn: Long) {
2
    var title = "default value"
3
    set(value) {
4
        if (!value.isNotEmpty()) {
5
            throw IllegalArgumentException("Title must not be empty")
6
        }
7
        field = value
8
    }
9
}

You can see we defined our own setter method set(value) for the title right below the property definition—note that you can't modify this set() method signature because this is what the compiler expects as a custom property setter function.

The parameter value passed to the set method represents the actual value that was assigned to the property by users—you can change the parameter name if you wish, but value is much preferred. We validated the value by checking if the value is empty. If empty, stop execution and throw an exception; otherwise, reassign the value to a special field variable.

This special field variable field inside the set method is an alias for the backing field of the property—a backing field is just a field that is used by properties when you want to modify or use that field data. Unlike value, you can't rename this special field variable.

Creating a Custom Getter

It's very easy to create a custom getter for a property in Kotlin.

1
class Book (val isbn: Long) {
2
    var title = "default value"
3
    //... set method

4
    get() {
5
        return field.toUpperCase()
6
    }
7
}

Inside the get method, we simply return a modified field—in our case, we returned the book title in uppercase.

1
val book = Book(9780007477159)
2
book.title = "A Song of Ice and Fire"
3
println(book.title) // "A SONG OF ICE AND FIRE"

4
println(book.isbn) // 9780007477159

Note that each time we set a value to the title property, its set method block is executed—the same goes for the get method each time we retrieve it.

If you want to learn about member functions for a Kotlin class (the kind of function that is defined inside a class, object, or interface), visit the More Fun With Functions post in this series.

More on Constructors

As I discussed earlier, we have two types of constructors in Kotlin: primary and secondary. We have the freedom to combine both of them in a single class—as you can see in the example below:

1
class Car(val name: String, val plateNo: String) {
2
    var new: Boolean = true
3
4
    constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) {
5
        this.new = new
6
    }
7
}

Note that we can't declare properties inside a secondary constructor, as we did for the primary constructor. If we want to do this, we have to declare it inside the class body and then initialize it in the secondary constructor.

In the code above, we set the default value of the new property for the class Car (remember, new is not a keyword in Kotlin)—we can then use the secondary constructor to change it if we want. In Kotlin, every secondary constructor must call the primary constructor, or call another secondary constructor that calls the primary constructor—we use the this keyword to achieve that.

Note also that we can have multiple secondary constructors inside a class.

1
class Car(val name: String, val plateNo: String) {
2
    var new: Boolean? = null
3
    var colour: String = ""
4
5
    constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) {
6
        this.new = new
7
    }
8
9
    constructor(name: String, plateNo: String, new: Boolean, colour: String ) :
10
            this(name, plateNo, new) {
11
        this.colour = colour
12
    }
13
}

If a class extends a superclass, then we can use the super keyword (similar to Java) to call the superclass constructor (we'll discuss inheritance in Kotlin in a future post).

1
// directly calls primary constructor

2
val car1 = Car("Peugeot 504", "XYZ234")
3
// directly calls 1st sec. constructor

4
val car2 = Car("Peugeot 504", "XYZ234", false)
5
// directly calls last sec. constructor

6
val car3 = Car("Peugeot 504", "XYZ234", false, "grey") 

As I said earlier, for us to explicitly include a visibility modifier to a constructor in a class, we've got to include the constructor keyword—by default, constructors are public.

1
class Car private constructor(val name: String, val plateNo: String) {
2
//...

Here, we made the constructor private—this means that users can't instantiate an object using its constructor directly. This can be useful if you want users to instead call another method (a factory method) to do the creation of objects indirectly.

Code Execution Order in Constructors and Other Tidbits

We know that the primary constructor of a class in Kotlin cannot have any code. However, we can place the initialization code that we want to execute within initializer blocks that are marked using the init keyword. You can have multiple initialization blocks within a class body. They will be executed in the order in which they were created.

The parameters that you pass in the primary constructor will be available everywhere within the class body. For example, you can use them within initializer blocks or to set values for other properties. As you already know by now, parameters in the primary constructor can have the val or var keyword before them to signify whether they are read-only or mutable.

One thing that might confuse beginners is that the same is not true for secondary constructors. You cannot use the val and var keywords in secondary constructor definition as you do in primary constructors. This is because doing so will create a property for that class, and having an object whose properties vary based on the constructor used to create it is not a good idea.

The secondary constructors in a class are required to delegate to the primary constructor, and this happens before any code within the secondary constructor body is executed. Since the code inside initializer blocks is considered part of the primary constructor, it is always executed before the code inside the body of secondary constructors. It doesn't matter if the initializer block is defined after the secondary constructor.

1
import java.time.LocalDate;
2
3
class Person(val name: String, val birthYear: Int) {
4
5
    val currentDate: LocalDate = LocalDate.now()
6
    var age: Int = currentDate.getYear() - this.birthYear
7
    var old: Boolean = false
8
    var married: Boolean? = null
9
    var alive: Boolean? = null
10
11
    init {
12
        println("Executing first initializer block for ${this.name}.")
13
        if(this.age > 65) {
14
            this.old = true
15
            println("${this.name} is ${this.age} years old.")
16
        }
17
    }
18
 
19
    constructor(name: String, birthYear: Int, married: Boolean) : this(name, birthYear) {
20
        println("Calling first constructor for ${this.name}.")
21
        this.married = married
22
    }
23
 
24
    constructor(name: String, birthYear: Int, married: Boolean, alive: Boolean) : this(name, birthYear, married) {
25
        println("Calling second constructor for ${this.name}.")            
26
        this.alive = alive
27
        if(this.alive == true && this.married == true) {       
28
            println("${this.name} is alive and married inside second constructor!")
29
        }
30
    }
31
32
    init {
33
        println("Executing second initializer block for ${this.name}.")
34
        if(this.alive == null && this.married == null) {       
35
            println("Cannot confirm if ${this.name} is alive and married inside second initializer!")
36
        }
37
    }
38
}
39
40
fun main() {
41
    /*

42
    Executing first initializer block for Amanda.

43
    Amanda is 77 years old.

44
    Executing second initializer block for Amanda.

45
    Cannot confirm if Amanda is alive and married inside second initializer!

46
    Calling first constructor for Amanda.

47
    */
48
    val personOne = Person("Amanda", 1945, true)
49
50
51
    /*

52
    Executing first initializer block for Alex.

53
    Executing second initializer block for Alex.

54
    Cannot confirm if Alex is alive and married inside second initializer!

55
    Calling first constructor for Alex.

56
    Calling second constructor for Alex.

57
    Alex is alive and married inside second constructor!

58
    */
59
    val personTwo = Person("Alex", 1980, true, true)
60
}

In the above code, we pass three parameters to instantiate a Person "Amanda". The secondary constructor definition with three parameters invokes the primary constructor with this(name, birthYear). Since initializer blocks are part of the primary constructor, they are executed next, and they are executed in the order in which we defined them.

When we instantiate the second Person "Alex", we go through the same process. This time, the second secondary constructor invokes the first secondary constructor, which in turn invokes the primary constructor. You should note that even though we pass the value of married and alive as true during instantiation, these properties only become true when the code within the second secondary constructor is executed. As a result, they are still null during the execution of the second initializer block. That is why we are unable to confirm if Alex is married and alive at this point.

As soon as all the initializer code is executed, Kotlin executes the code inside the secondary constructors. Once we execute the code inside the second secondary constructor, we can confirm that Alex is in fact both alive and married.

2. Any and Nothing Types

In Kotlin, the topmost type in the type hierarchy is called Any. This is equivalent to the Java Object type. This means that all classes in Kotlin explicitly inherit from the Any type, including String, Int, Double, and so on. The Any type contains three methods: equals, toString, and hashcode.

We can also utilize the Nothing class in Kotlin in functions that always return an exception—in other words, for functions that don't terminate normally. When a function returns Nothing, then we know it's going to throw an exception. No equivalent type of this kind exists in Java.

1
fun throwException(): Nothing {
2
    throw Exception("Exception message)
3
}

This can come in handy when testing error handling behavior in your unit tests.

3. Visibility Modifiers

Visibility modifiers help us to restrict the accessibility of our API to the public. We can provide different visibility modifiers to our classes, interfaces, objects, methods, or properties. Kotlin provides us with four visibility modifiers:

Public

This is the default, and any class, function, property, interface, or object that has this modifier can be accessed from anywhere.

Private

A top-level function, interface, or class that is declared as private can be accessed only within the same file.

Any function or property that is declared private inside a class, object, or interface can only be visible to other members of that same class, object, or interface.

1
class Account {
2
    private val amount: Double = 0.0
3
}

Protected

The protected modifier can only be applied to properties or functions inside a class, object, or interface—it can't be applied to top-level functions, classes, or interfaces. Properties or functions with this modifier are only accessible within the class defining it and any subclass.

Internal

In a project that has a module (Gradle or Maven module), a class, object, interface, or function specified with the internal modifier declared inside that module is only accessible from within that module.

1
internal class Account {
2
    val amount: Double = 0.0
3
}

4. Smart Casting

Casting means taking an object of another type and converting it into another object type. For example, in Java, we use the instanceof operator to determine whether a particular object type is of another type before we then cast it.

1
/* Java */
2
if (shape instanceof Circle) {
3
    Circle circle = (Circle) shape;
4
    circle.calCircumference(3.5); 
5
}

As you can see, we checked if the shape instance is Circle, and then we have to explicitly cast the shape reference to a Circle type so that we can call methods of the circle type.

Another awesome thing about Kotlin is the smartness of its compiler when it comes to casting. Let's now see a version in Kotlin.

1
/* Kotlin */
2
if (shape is Circle) {
3
    shape.calCircumference(3.5)
4
}

Pretty neat! The compiler is smart to know that the if block will be executed only if the shape object is an instance of Circle—so the casting mechanism is done under the hood for us. We can now easily call properties or functions of the Circle type inside the if block.

1
if (shape is Circle && shape.hasRadius()) {
2
    println("Circle radius is {shape.radius}")
3
}

Here, the last condition after the && in the if header will be called only when the first condition is true. If the shape is not a Circle, then the last condition won't be evaluated.

5. Explicit Casting

We can use the as operator (or unsafe cast operator) to explicitly cast a reference of a type to another type in Kotlin.

1
val circle = shape as Circle
2
circle.calCircumference(4)

If the explicit casting operation is illegal, note that a ClassCastException will be thrown. To prevent an exception from being thrown when casting, we can use the safe cast operator (or nullable cast operator) as?.

1
val circle: Circle? = shape as? Circle

The as? operator will try to cast to the intended type, and it returns null if the value can't be cast instead of throwing an exception. Remember that a similar mechanism was discussed in the Nullability section in Nullability, Loops, and Conditions post in this series. Read up there for a refresher.

6. Objects

Objects in Kotlin are more similar to JavaScript objects than Java objects. Note that an object in Kotlin is not an instance of a specific class!

Objects are very similar to classes. Here are some of the characteristics of objects in Kotlin:

  • They can have properties, methods, and an init block.
  • These properties or methods can have visibility modifiers.
  • They can't have constructors (primary or secondary).
  • They can extend other classes or implement an interface.

Let's now dig into how to create an object.

1
object Singleton {
2
    
3
    fun myFunc(): Unit {
4
        // do something

5
    }
6
}

We place the object keyword before the name of the object we want to create. In fact, we are creating singletons when we create objects in Kotlin using the object construct, because only one instance of an object exists. You'll learn more about this when we discuss object interoperability with Java.

A singleton is a software design pattern that guarantees a class has one instance only and a global point of access to it is provided by that class. Any time multiple classes or clients request the class, they get the same instance of the class. You can check out my post about the singleton pattern in Java to learn more about it.

You can access the object or singleton anywhere in your project—so long as you import its package.

1
Singleton.myFunc()

If you're a Java coder, this is how we typically create singletons:

1
public class Singleton  {
2
 
3
    private static Singleton INSTANCE = null;
4
 
5
    // other instance variables can be here

6
     
7
    private Singleton() {};
8
 
9
    public static synchronized Singleton getInstance() {
10
        if (INSTANCE == null) {
11
            INSTANCE = new Singleton();
12
        }
13
        return(INSTANCE);
14
    }
15
     
16
    // other instance methods can follow 

17
}

As you can see, using the Kotlin object construct makes it concise and easier to create singletons.

Objects in Kotlin can be utilized also to create constants. Typically in Java, we create constants in a class by making it a public static final field like this:

1
public final class APIConstants {
2
   
3
   public static final String baseUrl = "https://www.myapi.com/";
4
5
   private APIConstants() {}
6
}

This code in Java can be converted to Kotlin more succinctly like this:

1
package com.chike.kotlin.constants
2
3
object APIConstants {
4
    val baseUrl: String = "https://www.myapi.com/"
5
}

Here we declared the constant APIConstants with a property baseUrl inside a package com.chike.kotlin.constants. Under the hood, a Java private static final member baseUrl is created for us and initialized with the string URL.

To use this constant in another package in Kotlin, simply import the package.

1
import com.chike.kotlin.constants.APIConstants
2
3
APIConstants.baseUrl

Java Interoperability

Kotlin converts an object to a final Java class under the hood. This class has a private static field INSTANCE which holds a single instance (a singleton) of the class. The following code shows how simply users can call a Kotlin object from Java.

1
/* Java */
2
Singleton.INSTANCE.myFunc()

Here, a Java class called Singleton was generated with a public static final member INSTANCE, including a public final function myFunc().

To make the object function or property in Kotlin be a static member of the generated Java class, we use the @JvmStatic annotation. Here's how to use it:

1
object Singleton {
2
    
3
    @JvmStatic fun myFunc(): Unit {
4
        // do something

5
    }
6
}

By applying the @JvmStatic annotation to myFunc(), the compiler has turned it into a static function.

Now Java callers can call it like a normal static member call. Note that using the INSTANCE static field to call members will still work.

1
/* Java */
2
Singleton.myFunc()

7. Companion Objects

Now we've gotten to understand what objects are in Kotlin, let's dive into another kind of object, called companion objects.

Because Kotlin doesn't support static classes, methods, or properties like the ones we have in Java, the Kotlin team provided us with a more powerful alternative called companion objects. A companion object is basically an object that belongs to a class—this class is known as the companion class of the object. This also means that the characteristics I mentioned for objects also apply to companion objects.

Creating a Companion Object

Similar to static methods in Java, a companion object is not associated with a class instance but rather with the class itself—for example, a factory static method, which has the job of creating a class instance.

1
class Person private constructor(var firstName: String, var lastName: String) {
2
3
    companion object {
4
        fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
5
    }
6
}

Here, we made the constructor private—this means that users outside the class can't create an instance directly. Inside our companion object block, we have a function create(), which creates a Person object and returns it.

Invoking a Companion Object Function

companion object instantiation is lazy. In other words, it will be instantiated only when needed the first time. The instantiation of a companion object happens when an instance of the companion class is created or the companion object members are accessed.

Let's see how to invoke a companion object function in Kotlin.

1
val person = Person.create("Cersei", "Lannister")
2
println(person.firstName) // prints "Cersei"

As you can see, this is just like invoking a static method in Java as normal. In other words, we just call the class and then call the member. Note that apart from functions, we can also have properties inside our companion object.

1
class Person private constructor(var firstName: String, var lastName: String) {
2
    init {
3
        count++
4
    }
5
    
6
    companion object {
7
        var count: Int = 0
8
        fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
9
        
10
        init {
11
            println("Person companion object created")
12
        }
13
    }
14
15
}

Note also that the companion class has unrestricted access to all the properties and functions declared in its companion object, whereas a companion object can't access the class members. We can have an init code block inside a companion object—this is called immediately when the companion object is created.

1
Person.create("Arya", "Stark")
2
Person.create("Daenerys", "Targaryen")
3
println(Person.count)

The result of executing the code above will be:

1
Person companion object created
2
2

Remember, only a single instance of a class companion object can ever exist.

We're also free to provide our companion object with a name.

1
// ...

2
companion object Factory {
3
    var count: Int = 0
4
    fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
5
}
6
// ... 

Here, we gave it a name called Factory. We can then call it like this in Kotlin:

1
Person.Factory.create("Petyr", "Baelish")

This style is verbose, so sticking with the previous way is much preferred. But this might come in handy when calling a companion object function or property from Java.

As I said earlier, like objects, companion objects can also include properties or functions, implement interfaces, and even extend a class.

1
interface PersonFactory {
2
    fun create(firstName: String, lastName: String): Person
3
}
4
5
class Person private constructor(var firstName: String, var lastName: String) {
6
    
7
    companion object : PersonFactory {
8
        override fun create(firstName: String, lastName: String): Person {
9
            return Person(firstName, lastName)
10
        }
11
    }
12
}

Here, we have an interface PersonFactory with just a single create() function. Looking at our new modified companion object, it now implements this interface (you'll learn about interfaces and inheritance in Kotlin in a later post).

Java Interoperability

Under the hood, companion objects are compiled similarly to the way a Kotlin object is compiled. In our own case, two classes are generated for us: a final Person class and an inner static final class Person$Companion.

The Person class contains a final static member called Companion—this static field is an object of the Person$Companion inner class. The Person$Companion inner class also has its own members, and one of them is a public final function called create().

Note that we did not give our companion object a name, so the generated static inner class was Companion. If we had given it a name, then the generated name would be the name we gave it in Kotlin.

1
/* Java */
2
Person person = Person.Companion.create("Jon", "Snow");

Here, the companion object in Kotlin has no name, so we use the name Companion provided by the compiler for Java callers to call it.

The @JvmStatic annotation applied on a companion object member works similarly to how it works for a regular object.

Companion Object Extensions

Similarly to how extension functions can extend the functionality of a class, we can also extend the functionality of a companion object. (If you want a refresher on extension functions in Kotlin, visit the Advanced Functions tutorial in this series).

1
class ClassA {
2
3
    companion object  {
4
5
    }
6
}
7
8
fun ClassA.Companion.extFunc() {
9
    // ... do implementation

10
}
11
12
ClassA.extFunc()

Here, we defined an extension function extFunc() on the companion object ClassA.Companion. In other words, extfunc() is an extension of the companion object. Then we can call the extension as if it's a member function (it's not!) of the companion object.

Behind the scenes, the compiler will create a static utility function extFunc(). The receiver object as an argument to this utility function is ClassA$Companion.

Conclusion

In this tutorial, you learned about basic classes and objects in Kotlin. We covered the following about classes:

  • class creation
  • constructors
  • properties
  • visibility modifiers
  • smart casting
  • explicit casting

Also, you learned about how objects and companion objects in Kotlin can easily replace the static methods, constants, and singletons you code in Java. But that's not all! There is still more to learn about classes in Kotlin. In the next post, I'll show you even more cool features that Kotlin has for object-oriented programming. See you soon!

To learn more about the Kotlin language, I recommend visiting the Kotlin documentation. Or check out some of our other Android app development posts here on Envato Tuts+!

This post has been updated with contributions from Nitish Kumar. Nitish is a web developer with experience in creating eCommerce websites on various platforms. He spends his free time working on personal projects that make his everyday life easier or taking long evening walks with friends.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.