Kotlin Inheritance

Inheritance in Kotlin.

fun main() {
    val laserPrinter = LaserPrinter("Brother 1234", 15)
    laserPrinter.printModel()
    SomethingElse("Hey")
}

open abstract class Printer(val modelName: String) { // Must be declared open as everything is final by default, therefore not extendable. Abstract in this case, which makes 'open' declaration redundant. () added to create primary constructor
    open fun printModel() = println("The model of this printer is $modelName") // Must be open to be overridden
    abstract fun bestSellingPrice(): Double // Does not need to be declared open as is abstract
}

open class LaserPrinter(modelName: String, ppm: Int): Printer(modelName) { // To extend a class in Kotlin use the : annotation. Requires Printer to be initialised as Printer has constructor by default, so () added after both LaserPrinter and Printer
    // We do not have to match constructor signatures (see ppm added in LaserPrinter not present in Printer)
//    constructor(): super() // How to initialise the Printer class if we don't want a primary constructor in Printer i.e. it doesn't have () in the first line
    final override fun printModel() = println("The model of this laser printer is $modelName") // Overrides the super implementation. Final has been added to prevent further overriding

    override fun bestSellingPrice(): Double = 129.99 // Pretend this is an actual function that does something. Still need to use override for abstract functions
    // When a function is overridden it is declared open, therefore can be overridden by subclasses. If not desirable,

}

class specialLaserPrinter(modelName: String, ppm: Int): LaserPrinter(modelName, ppm) { // LaserPrinter needs to be open to subclass
    override fun bestSellingPrice(): Double { // bestSellingPrice can be overridden
        return super.bestSellingPrice() + 5
    }
//    override fun printModel() // Won't work because printModel() is final in LaserPrinter
}


// Secondary constructors (which can take a function where the primary cannot) in inherited classes only make sense if no primary constructor is called, as so:
open class Something { // No primary constructor
    val someProperty: String

    constructor(someParameter: String) {
        someProperty = someParameter
        println("I'm in the parent's constructor")
    }
}

class SomethingElse: Something { // No primary constructor

    constructor(someOtherParameter: String): super(someOtherParameter) {
        println("I'm in the child's constructor")
    }
}

//open data class DataClass(val number: Int) {} // Data classes are closed-typed cannot extended, abstract or inner classes. Can inherit FROM other classes, but stops there.

 

Kotlin Access Modifiers & Constructors

Kotlin Access Modifiers & Constructors – also see Java comparison of top-level modifiers

fun main() {
    // Public by default, also private, protected and internal
    // Can't use protected at the top level
    // Can have multiple (top-level) classes in one file, unlike Java. Hence you can have private classes which are only accessible from the same file
    val person  = Person()
    println(person)
    // Internal means accessible from within the same module. You can have more than one module in a project.

    // In Kotlin, classes can't see private members belonging to inner classes
    // When compiling, the JVM compiles private -> package-private, internal -> public, but Kotlin enforces its visibility rules so it won't be broken
    // However, when mixing Java and Kotlin may result in Java being able to access stuff in Kotlin it shouldn't e.g. internal should only access in same module, but ANY Java code can access it as is compiled to public. Look out for this - typically the name will be very long and ugly when trying to access from Java. Probably shouldn't be doing!

    // All classes are public and final by default. Have constructor created by default.

    val emp = Employee("John")
    println(emp.firstName)

    val emp1 = Employee1("Jack")
    println(emp1.firstName)

    val emp2 = Employee2("Jane", false)
    println("${emp2.firstName} ${emp2.fullTime}")
    val emp2x = Employee2("Jill") // Default fullTime applied
    println("${emp2x.firstName} ${emp2x.fullTime}")

    val emp2a = Employee2a("Jane", false)
    println("${emp2a.firstName} ${emp2a.fullTime}")
    val emp2ax = Employee2a("Jill") // Default fullTime applied
    println("${emp2ax.firstName} ${emp2ax.fullTime}")

    emp2ax.fullTime = false // Demonstrate setter
//    emp2x.firstName = "Joan" // Not allowed as firstName is a val

    println(Demo().dummy)

    val emp3 = Employee3("Jim", false)
//    println(emp3.firstName) // Invalid. firstName is private, so how do we get or set? In Java we could allocate getters and setters and would allow us access. In Kotlin, default getters and setters are blocked in private - can't be done.
    // Therefore no point in designating properties private if needing to be changed. Fine to have public properties - members outside will not be able to change them.

    val emp4 = Employee4("Jill") // Default fullTime applied
    emp4.fullTime // Calls custom getter
    emp4.fullTime = false // Calls custom setter


}

private class Person {

}

// This is equivalent to the Employee Java class in this module. **The long way**
class Employee constructor(firstName: String){ // Declare constructor on same line as class - this is the primary constructor.

    val firstName: String // Declaration

    init { // NOT a constructor. This block runs when the class is first instantiated. Can have multiple, run in order.
        this.firstName = firstName
    }

}

//The Kotlin way
class Employee1 (val firstName: String) // Declaration inline with constructor
// If you wanted to declare access modifier for constructor, you would need 'constructor' keyword:
// class Employee2 protected constructor(val firstName: String)

// This is equivalent to the Employee2 Java class in this module. **The long way**
class Employee2 (val firstName: String) {
    var fullTime: Boolean = true // Cannot be null. Only need this if using the variable outside the constructor body
    constructor(firstName: String, fullTime: Boolean): this(firstName){
        this.fullTime = fullTime
    } // Secondary constructor. Calls the primary and feeds in firstName. Cannot declare properties in secondary constructor
}

//The Kotlin way
class Employee2a (val firstName: String, var fullTime: Boolean = true) // No need for multiple constructors as default is declared

//Demo of class without primary constructor, but with secondary. Because you can run expression in secondaries but not primaries
class Demo {
    val dummy: String

    constructor() {
        dummy = "hello"
    }
}

// Up until now all properties have been (default) public. Here is an Employee class with private properties:
class Employee3 (private val firstName: String, private var fullTime: Boolean = true) // Cannot access e.g. emp.firstName. Reason it worked before is Kotlin creates default getters and setters (see equivalent Employee3 Java class). Likely no need to do this as all properties are public & final by default and have to go through the getters and setters to access them.

// To customise the getters/setters (accessors) you cannot declare the property within the primary constructor, but in the class:
class  Employee4 (val firstName: String, fullTime: Boolean = true) { // Not declaring the property, just using it
    var fullTime = fullTime // Must declare getter/setter (accessor) immediately after declaring the property
    get() {
        println("We are getting fullTime")
        return field // The only time we ever use the 'field' identifier - refers to a backing field in custom accessor
    }
    set(value) {
     println("Running the custom set")
        field = value
    }
}

 

 

 

public class JavaEmployee {

    public final String firstName;

    public JavaEmployee (String firstName) {
        this.firstName = firstName;
    }
}

 

public class JavaEmployee2 {

    public final String firstName;
    private final boolean fullTime;

    public JavaEmployee2(String firstName) { // Alternative constructor which applies default fullTime
        this.firstName = firstName;
        this.fullTime = true;
    }

    public JavaEmployee2(String firstName, boolean fullTime) {
        this.firstName = firstName;
        this.fullTime = fullTime;
    }
}

 

public class JavaEmployee3 {

    public String firstName;
    private boolean fullTime;

    public JavaEmployee3(String firstName) { // Alternative constructor which applies default fullTime
        this.firstName = firstName;
        this.fullTime = true;
    }

    public JavaEmployee3(String firstName, boolean fullTime) {
        this.firstName = firstName;
        this.fullTime = fullTime;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public boolean isFullTime() {
        return fullTime;
    }

    public void setFullTime(boolean fullTime) {
        this.fullTime = fullTime;
    }
}