1. Overview

In this tutorial, we’re going to talk about inheritance rules and the open keyword in Kotlin.

First, we’ll start with a little bit of philosophy on inheritance. Then we’ll switch gears to see how the open keyword affects classes, methods, and properties in Kotlin. Finally, we’ll take a look at how the open keyword and enterprise frameworks such as Spring interact with each other.

2. Design for Inheritance

In Java, classes and methods are open for extension by default. This means that we can extend from any class or override any methods in Java unless they’re marked as final. Because of the close relationship entailed by the inheritance and the risks of such a relation, Joshua Bloch dedicated a whole item to this in Effective Java:

Design and document for inheritance or else prohibit it

This means that classes and methods should be sealed and not extensible or overrideable unless we have a good reason to extend or override them. And when we decide our class should be open for extension, we should document the effects of overriding any method.

The better practice favors being final by default, and yet the implementation has another idea.

Let’s see how Kotlin approaches the same concept.

3. Class Hierarchies

In Kotlin, everything is final by default. So, if we try to extend from any class in its default configuration:

class Try
class Success : Try()

Then the compiler will fail:

Kotlin: This type is final, so it cannot be inherited from

To make a class open for extension, we should mark that class with the open keyword:

open class Try
class Success : Try()

Now we can extend the Try class. If we need to extend the Success class, we should mark it with the open keyword also. Therefore, the open keyword doesn’t have a transitive effect on the classes.

When we don’t mark classes with the open keyword, then the Kotlin compiler emits a final class at the bytecode level.

Let’s verify this using a trivial class:

class Sealed

And now, let’s examine the generated bytecode:

$ kotlinc Inheritance.kt 
$ javap -c -p -v com.baeldung.inheritance.Sealed 
Compiled from "Inheritance.kt"
public final class com.baeldung.inheritance.Sealed
// truncated

As we can see, this class is just like a final class in Java.

4. Overriding Methods and Properties

Similar to classes, by default, methods and properties are final in Kotlin, even if the enclosing class is open. For instance:

open class Try {
    fun isSuccess(): Boolean = false
}
class Success : Try() {
    override fun isSuccess(): Boolean = true
}

The compiler won’t be able to compile the Success class because we can’t override a final method:

Kotlin: 'isSuccess' in 'Try' is final and cannot be overridden

Similar to classes, the compiler emits a final method here:

$ kotlinc Inheritance.kt
$ javap -c -p -v com.baeldung.inheritance.Try
// truncated
public final boolean isSuccess();
   descriptor: ()Z
   flags: (0x0011) ACC_PUBLIC, ACC_FINAL

To override a method in subclasses, we should mark that method with the open keyword in the superclass:

open class Try {
    open fun isSuccess(): Boolean = false
}
class Success : Try() {
    override fun isSuccess(): Boolean = true
}

The same is true for properties. They are final unless we mark them with the open keyword:

open class Try {
    open val value: Any? = null
    // omitted
}
class Success(override val value: Any?) : Try() {
    // omitted
}

As shown above, we’re overriding the property in the subclass constructor.

Methods and properties marked with the override keyword are implicitly open so that we can override them in subclasses. To prevent this, we can mark them with final:

open class Success(override val value: Any?) : Try() {
    final override fun isSuccess(): Boolean = true
}

As shown above, the subclasses of the class Success can’t override the isSuccess() method.

5. Framework Interoperability

Many enterprise frameworks in the JVM ecosystem, such as Spring and Hibernate, depend on inheritance to create dynamic proxies. So we can’t use final Kotlin classes as Spring beans or JPA entities.

One way to resolve this issue to mark every Spring bean or JPA entity with the open keyword. We may need to mark their advised methods and properties, too:

@Service
@Transactional
open class UserService {

    open fun register(u: User) {
        // omitted
    }
}

Marking every special class and its members with the open keyword is an inconvenient and ineffective approach. To improve this situation, JetBrains provides the Kotlin-allopen plugin. This plugin adapts Kotlin classes to the requirements of the frameworks like Spring.

Basically, this plugin marks certain classes and their members with the open keyword without an explicit open. For this to happen, those classes should be annotated with special Spring annotations such as @Service or @Entity:

@Service
@Transactional
class UserService {

    fun register(u: User) {
        // omitted
    }
}

Even though we didn’t add those open keywords explicitly, the compiler plugin will add them during compilation.

6. Conclusion

In this tutorial, we saw how inheritance works in Kotlin. Also, we learned how the open keyword affects class hierarchies in Kotlin.

As usual, all the examples are available over on GitHub.

guest
0 Comments
Inline Feedbacks
View all comments