The Programmers Guide To Kotlin - Inheritance
Written by Mike James   
Monday, 07 August 2017
Article Index
The Programmers Guide To Kotlin - Inheritance
Interfaces
Delegation

Inheritance it once was the whole point of object-oriented programming. It was the big advantage and it promised easy code reuse. The reality wasn't so good, but it is still amazingly valuable as long as you use it correctly. Kotlin has facilities to help you control and work with both inheritance and, one alternative, composition.

 

Programmer's Guide To Kotlin Third Edition

kotlin3e360

You can buy it from: Amazon

Contents

  1. What makes Kotlin Special
  2. The Basics:Variables,Primitive Types and Functions 
  3. Control
         Extract: If and When 
  4. Strings and Arrays
  5. The Class & The Object
  6. Inheritance
  7. The Type Hierarchy
  8. Generics
  9. Collections, Iterators, Sequences & Ranges
        Extract: Iterators & Sequences 
  10. Advanced functions 
  11. Anonymous, Lamdas & Inline Functions
  12. Data classes, enums and destructuring
        Extract: Destructuring 
  13. Exceptions, Annotations & Reflection
  14. Coroutines
        Extract: Coroutines 
  15. Working with Java
        Extract: Using Swing
  16. Compose Multiplatform
        Extract: Compose Layout ***NEW!

<ASIN:B0D8H4N8SK>

 

If you have a class then you can use this to create as many instances of the object that the class defines as you like. This is the whole point of using a class to define an object. Creating objects was the subject of the previous chapter. In this chapter we are going to look at how classes can be used in more sophisticated ways to define objects.

Copy & Paste v Inheritance

If you want to create a class that is very similar to a class that you already have then the primitive way of doing the job is to simply copy and paste the code in the base or super class to create the derived or sub class. Copy and paste is simple but if you make any changes to the base class these are not reflected in the super class unless you repeat the copy and paste and the modifications you made. 

To overcome this problem the idea of inheritance was invented. You can give a new class all of the members of an existing class by making it inherit from the existing class. This means you can have code reuse without copy and paste and the proposed big advantage of this is that any changes to the super class are automatically communicated to the sub class.

This one big advantage has proved over the years to be a two edged sword. The problem is often called "brittle base classes". If a base class isn't specifically designed to be used in an inheritance chain then small changes to the way it works can break most or all of the classes that are derived from it.

When a class is used as a base class it changes its importance. Imagine you are working on refactoring a class that isn't used as a base class. You are fairly free to do to it whatever you like as long as the class works. Now change it into a base class for hundreds of other derived classes. Any change that you make to it might just have an important effect on any of the derived classes and you just have to trust that everything is ok. 

You can control inheritance so that it is much safer by controlling what is visible outside of a class. Roughly speaking the idea is that you don't allow the inner workings of the class to leak out. Derived classes can only rely on what the base class does and not how it does it. If this is true then you can refactor the base class to your hearts content safe in the knowledge that as long as the result does the same job the derived classes will be unaffected. All this is possible but it does require some extra thought and some extra work. 

When languages such as Java were invented the enthusiasm for object oriented programing was overwhelming and they embraced the idea completely. Today Kotlin takes a more cautious approach to inheritance and provides as much protection against the pitfalls as it possibly can.

As a result Kotlin's object-oriented features are very similar to Java's but they differ in many small but important ways. 

Inheritance

Kotlin classes support single inheritance.

This means any class can inherit all of the members of one single class. Of course that single class might have inherited from another single class so resulting in a chain of inheritance.

To indicate which class is the super class you simply write its name after a colon in the class declaration. For example:

class myClass:mySuperClass{...

If you don't specify a SuperClass then the default class is Any which is the fundamental class in Kotlin.

In Java any class that you define can be used as a superclass unless you mark it as final - in which case it is not eligible to be used to create a derived class. 

The  big difference with Kotlin is that all classes are considered as final i.e. cannot be inherited from unless otherwise stated. This is the opposite of most languages which expect the programmer to explicitly indicate a class not intended to act as a base class.

To mark a class as suitable for inheritance you have to add the keyword open to its declaration.

open myClass {...

The thinking behind this is that inheritance it is too serious a matter to enter into without designing for it. If you create a class then the default is that it will not be used as the base class for anything else. It is what it is and it will not lead to a set of other derived classes. If you do want to use it as a base class then you need to add open to its declaration and the hope is that this might make you think about the design of the class. 

Inheritance is easy to implement but difficult to manage.

Inherited Constructors

Inheritance is easy, but you have to deal with the problem that the base class needs to be constructed before your derived class can add its extras and its modifications.

You  have to arrange to call the base class's constructor and this is something you have to do, even if the base class appears not to have an explicit constructor.

Due to the use of primary and secondary constructors, the rules for calling inherited constructors can seem quite complicated at first, but they are very logical. The key ideas are that the primary constructor of the base class always has to be called and how this happens depends on whether or not the derived class also has an explicit primary constructor. 

If a class has a primary constructor then all of the secondary constructors have to call it.

This means that if the derived class has a primary constructor then it has to call the base class's primary constructor if it has one, or a secondary constructor, which calls the base class's primary,  if it doesn't.

his ensures that any constructor in the derived class always calls the base class's primary constructor eventually.

If the derived class doesn't have an explicit primary constructor then it is down to each of the secondary constructors to call one of the base class's constructors using the super keyword. Once again this will call the base class's primary constructor.

You can now see that the rule is that any secondary constructor in the derived class either has to call its own primary constructor, i.e. this() or, if there isn't a primary constructor, one of the super classes constructors, i.e. super().

Which particular secondary constructor is called depends on the signature of the parameters.  

Let's see how this works. For example:

 open class MyClassA {}
 class MyClassB: MyClassA() {}

In this case the base class doesn't have any constructors and so the system creates a default parameterless constructor which the derive class calls from its primary constructor. No explicit primary constructor is needed because the default parameterless one will do.

If the base class has a primary constructor then it has to be called with appropriate parameter values:

open class MyClassA(name:String) {}
class MyClassB: MyClassA("Micky Mouse") {}

If both classes have primary constructors and the derived class has a secondary constructor then it has to call the derived classes secondary constructor, which in turn calls the base class's primary constructor:

open class MyClassA(name: String) {}class MyClassB(name:String): MyClassA(name) {
    constructor(name: String, telephone: String)
                                   : this(name) {
        println("Hello")
    }
}

If the derived class doesn't have a primary constructor then the secondary constructors have to call a base class constructor for them selves.

For example:

open class MyClassA(name: String) {
     constructor(name: String, address: String)
                                    : this(name) {
            println(name)
     }
}
class MyClassB:MyClassA {
  constructor(name: String, telephone: String)
                        : super(name,"a street") {
            println("Hello")
  }
}

In this case the derived class secondary constructor calls super because there is no derived primary to call. This in turn causes the base class constructor to call the base class primary constructor if there is one.

The derived class can also call the base class primary constructor directly using the correct signature.  The secondary constructor can also call another constructor as long as it eventually results in the base or derived class primary constructor being called.

<ASIN:1871962536>



Last Updated ( Sunday, 10 September 2017 )