ref – https://beginnersbook.com/2019/03/kotlin-sealed-class/
Category Archives: Kotlin/IntelliJ
Kotlin Inheritance
ref – https://www.programiz.com/kotlin-programming/inheritance
Why do we inherit?
Suppose, in your application, you want three characters – a math teacher, a footballer and a businessman.
Since, all of the characters are persons, they can walk and talk. However, they also have some special skills. A math teacher can teach math, a footballer can play football and a businessman can run a business.
In each of the classes, you would be copying the same code for walk and talk for each character. This means you’d be implementing walk and talk three different times for our three characters.
And to make matters worse, if you want to add a new feature (i.e eat) you need to implement the eat three different times (once for each character!). This can easily become error prone (when copying) and duplicate codes.
It would be a lot easier if we had a Person class with basic features like talk, walk, eat, sleep, and add special skills to those features as per our characters. This is done using inheritance.
Using inheritance, now we don’t need to repeatedly implement the same code for walk(), talk() and eat() for each class. We just implement a class with all these common features. Then when we create each charaeter, we simlpy need to inherit from our common class.
So, for MathTeacher (derived class), you inherit all features of a Person (base class) and add a new feature teachMath(). Likewise, for the Footballer class, you inherit all the features of the Person class and add a new feature playFootball() and so on.
This makes your code cleaner, understandable and extendable.
Is A
It is important to remember: When working with inheritance, each derived class should satisfy the condition whether it is a base class or not.
In the example above, MathTeacher is a Person, Footballer is a Person.
You cannot have something like, Businessman is a Business.
Make it inheritable by using Open
By default, classes in Kotlin are final. A final class cannot be subclassed. By using the open annotation on a class, compiler allows you to derive new classes from it.
Hence, if we want to inherit from say class A, we MUST put open in front of class A.
We have a primary constructor
1 2 3 4 5 6 |
open class Person(age: Int, name: String) { init { println("My name is $name.") println("My age is $age") } } |
We then declare our characters. In our primary constructor, we extend from class Person.
We then pass in our primary constructor’s parameters age and name into our parent class Person.
1 2 3 4 5 6 |
class MathTeacher(age: Int, name: String): Person(age, name) { fun teachMaths() { println("I teach in primary school.") } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Footballer(age: Int, name: String): Person(age, name) { fun playFootball() { println("I play for LA Galaxy.") } } fun main(args: Array<String>) { val t1 = MathTeacher(25, "Jack") t1.teachMaths() println() val f1 = Footballer(29, "Christiano") f1.playFootball() } |
Child primary constructor must use base
If the derived class has a primary constructor, the base class must be initialized using the parameters of derived class’s primary constructor.
The base class must be executed first. Then, the derived primary constructor executes.
In the above program, both derived classes have two parameters age and name, and both these parameters are initialized in primary constructor in the base class.
In our next example, we have base class Person. We want it to be the base class where other classes can derive from it so we use open.
We then give Person a constructor.
1 2 3 4 5 6 7 8 9 10 |
open class Person { var age: Int = 0 var name: String = "" constructor(newAge: Int, newName: String) { println("Person constructor (newAge, newName)") age = newAge name = newName } } |
We then create Footballer which extends Person. In order for this extending to take place, we must put the base class behind our Footballer class like so:
1 |
class Footballer(age: Int, name: String, club: String): Person(age, name) {...} |
This means Person constructor gets executed first.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
open class Person { var age: Int = 0 var name: String = "" constructor(newAge: Int, newName: String) { println("Person constructor (newAge, newName)") age = newAge name = newName } } // primary constructor // calls Parent class constructor class Footballer(age: Int, name: String, club: String): Person(age, name) { init { println("Football player $name of age $age and plays for $club.") } fun playFootball() { println("I am playing football.") } } fun main(args: Array<String>) { val f1 = Footballer(29, "Cristiano", "LA Galaxy") } |
output:
Person constructor (newAge, newName)
Football player Cristiano of age 29 and plays for LA Galaxy.
Child that has no primary constructor
In case of no primary constructor, each base class has to initialize the base (using super keyword), or delegate to another constructor which does that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
open class Log { var data: String = "" var numberOfData = 0 constructor(_data: String) { println("Log - 1st constructor - with parameter $_data") } constructor(_data: String, _numberOfData: Int) { data = _data numberOfData = _numberOfData println("Log - 2nd constructor - $data: $numberOfData times") } } class AuthLog: Log { // calls another of its own constructor that delegates to the base class constructor(_data: String): this("From AuthLog -> + $_data", 10) { println("AuthLog - first constructor with parameter $_data") } // calls parent class's constructor FIRST // after that, it will execute the below constructor which delegates to super constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) { println("AuthLog - second constructor with parameter $_data, $_numberOfData") } } fun main(args: Array<String>) { val p1 = AuthLog("Bad Password") } |
Constructor in Kotlin
ref – https://www.programiz.com/kotlin-programming/constructors
In Kotlin, a class can also contain one or more secondary constructors. They are created using constructor keyword.
The most common use of secondary constructor comes up when you need to extend a class that provides multiple constructors that initialize the class in different ways.
For example, you want to initialize your class with absolutely no parameters. And then other instances where you want to initialize it with multiple parameters.
1 2 3 4 5 6 7 8 |
class Log { constructor(data: String) { // some code } constructor(data: String, numberOfData: Int) { // some code } } |
In our Log example, we have two secondary constructors. No primary constructor.
Constructor calling constructor
In Kotlin, you can also call a constructor from another constructor of the same class using this().
In our case, say for a 1 parameter constructor, we call a 2 parameter constructor right after the one parameter constructor finishes running.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Log { constructor(data: String) { // code } constructor(data: String, numberOfData: Int) { // code } } class AuthLog: Log { constructor(data: String): this(data, 10) { // code, this runs first, then calls the constructor with the two parameter below } constructor(data: String, numberOfData: Int): super(data, numberOfData) { // code, after running, calls Log's constructor } } |
Over-riding rules in Kotlin
First, we have an abstract class. Remember that our abstract functions/properties MUST be over-ridden.
1 2 3 4 5 6 7 |
// create abstract class // over-riding properties and functions is a MUST abstract class Course(private val topic: String, private val price: Double) { open fun learn() { println("For the price of $price, you can learn $topic") } } |
In order to use this abstract class, we can use an open class. An open class means it can be extended. It can also extend from abstract class Course. When we extend Course, we initialize its properties. We’re not happy with its default learn function, so we override it and implement it ourselves.
open in abstract means it is over-ridable. If there is no open keyword, then the default keyword ‘final’ applies.
As you can see, when we instantiate KotlinCourse, we execute learn function and see that it calls KotlinCourse’ learn function.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// over-riding properties and functions are OPTIONAL // We CAN instantiate open classes // we extend Course, which means we must over-ride learn open class KotlinCourse: Course("Kotlin", 999.99) { override fun learn() { println("Kotlin Course: You'll learn lots of awesome programming skills with KOTLIN!!!") } } fun main(args: Array<String>) { val course = KotlinCourse() course.learn() } |
What we have a specialized class SpecializedKotlinClass that extends KotlineCourse?
By default, we can override the learn function in KotlinCourse. If we DO NOT override it, it will simply call KotlinCourse’s learn function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
abstract class Course(private val topic: String, private val price: Double) { open fun learn() { println("For the price of $price, you can learn $topic") } } open class KotlinCourse: Course("Kotlin", 999.99) { override fun learn() { println("Kotlin Course: You'll learn lots of awesome programming skills with KOTLIN!!!") } } class SpecializedKotlinClass: KotlinCourse() { override fun learn() { println("Welcome to a Specialized version of our Kotlin Class!!!") } } fun main(args: Array<String>) { val course = KotlinCourse() course.learn() } |
However, say we don’t want people to over-ride KotlinCourse’s function. Any class that extends KotlinCourse MUST USE its learn function.
In order to prevent others to -override KotlinCourse’ learn function, simply put final override fun.
Thus, this prevents SpecializedKotlinClass from overriding the learn function. Doing so would give you a compiler error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
abstract class Course(private val topic: String, private val price: Double) { open fun learn() { println("For the price of $price, you can learn $topic") } } open class KotlinCourse: Course("Kotlin", 999.99) { final override fun learn() { println("Kotlin Course: You'll learn lots of awesome programming skills with KOTLIN!!!") } } class SpecializedKotlinClass: KotlinCourse() { override fun learn() { println("Welcome to a Specialized version of our Kotlin Class!!!") } } fun main(args: Array<String>) { val course = SpecializedKotlinClass() course.learn() } |
Multiple Super Types
Say we create another interface with a learn function:
And we extend both from abstract and interface…where both have function learn. When you use super, the compiler will be confused which super type you are referring to. In this case, we can use the bracket and the super type name to designate whether you want to use the interface or the abstract one.
1 2 3 4 5 6 7 8 |
open class KotlinCourse: Course("Kotlin", 999.99), Learnable { final override fun learn() { super<Learnable>.learn() super<Course>.learn() println("finised learning") } } |
interfaces in Kotlin
Interfaces define a contract to which different classes can follow. To do that they must override each function and property defined in the interface.
We first declare Buildable interface, which says whatever class conforms to me must declare:
1) val timeRequired of type Int
2) function build
1 2 3 4 |
interface Buildable { val timeRequired: Int fun build() } |
Interfaces cannot have state. Thus, the properties you declare in your interface cannot be initialized.
Classes that conform to these interfaces must declare and initialize this themselves.
1 2 3 |
class Car (val color: String): Drivable, Buildable { override val timeRequired = 10 // initialize the properties in the classes that conform to the interface } |
Besides declaring a Car type like so:
1 |
val myCar: Car = Car("Red") |
We can also have type of interface Buildable:
1 |
val car:Buildable = Car("Red") |
Which then we can only access the function build, and property timeRequired, as specified in the interface. If class Car conformed to other interfaces, we cannot access the functions of the other interfaces because our type is only for Buildable.
Say we a Truck class that also conforms to Buildable. Thus, we can simply have a reference of type Buildable, and call build on the objects like so
1 2 3 4 5 6 7 |
var vehicle: Buildable = null vehicle = Car("Red") vehicle.build() vehicle = Truck("yellow") vehicle.build() |
Now we are free to change the implementation of function build in class Car and Truck, without affecting this outer code, which does the building. In other words…we can freely change HOW the vehicle is built, without affecting the building of it.
Infix Functions in Kotlin
https://medium.com/makingtuenti/infix-functions-in-kotlin-2db3d3142dd2
https://www.programiz.com/kotlin-programming/infix-notation
https://github.com/http4k/http4k/blob/master/http4k-core/src/main/kotlin/org/http4k/routing/routing.kt
Say we have enums that represents the shapes of the cards.
1 2 3 4 5 6 |
enum class Suit { HEARTS, SPADES, CLUBS, DIAMONDS } |
We also have enums that represent the numbers (or ranking) of the cards.
1 2 3 4 5 |
enum class Rank { TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE } |
1 |
data class Card(val rank: Rank, val suit: Suit) |
In order to create a card we have to do the following:
1 |
val card = Card(Rank.QUEEN, Suit.HEARTS) |
We can simplify the way of creating a new card so we’re going to add a function called of to Rank
For enum class Rank, we create a function called of.
It takes in a parameter of Suit object.
1 2 3 4 5 6 7 |
enum class Rank { TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE; fun of(suit: Suit) = Card(this, suit) } |
We declare that our function takes in parameter of type Suit. When we instantiate Card, we use this to reference our Rank object, and then just have the user pass in suit
Then just use it like so:
1 |
val card = Rank.QUEEN.of(Suit.HEARTS) |
‘this’ references Rank.Queen
‘suit’ references Suit.Hearts
We can still improve it if we use static imports for QUEEN and HEARTS to get the card creation closer to natural language. So you put the class enum in another class file.
For example, without static imports, we’d have to go
1 2 3 |
Math.sqrt(4) Math.pow(2,2) Math.abs(6.3) |
But WITH static imports, we don’t always have to reference Math anymore.
1 2 3 4 5 6 7 8 9 |
import static java.lang.Math.*; class Test2 { public static void main(String[] args) { System.out.println(sqrt(4)); System.out.println(pow(2, 2)); System.out.println(abs(6.3)); } } |
Hence, in our example, we can use static imports to simply access the enums value themselves:
1 |
val card = QUEEN.of(HEARTS) |
This is much better, but we can do more.
Let’s convert the of function in an infix function. We only have to add the reserved word infix at the beginning of the function:
1 2 3 4 5 6 7 |
enum class Rank { TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE; infix fun of(suit: Suit) = Card(this, suit) } |
An infix function add to the function the ability to use it with infix notation.
We can now do this:
1 2 3 4 |
// We call function 'of'. // QUEEN is parameter 'this' // HEARTS is the parameter suit val card = QUEEN of HEARTS |
Given
1 |
"hello" bind GET to { request:Request => Response ... } |
from definition:
1 |
infix fun String.bind(method: Method): PathMethod = PathMethod(this, method) |
where http4k added functionality ‘bind’ to String.
(https://github.com/http4k/api/blob/master/org.http4k.contract/kotlin.-string/index.md)
In this example, we declare the infix function name to be bind.
‘this’ is the String itself.
Hence this is how we get “hello” bind
Notice in the infix fun String.bind that it takes in a parameter of type Method. This is how we have “hello” bind GET
So now using infix notation, we provided a string that gets bound to a Method called GET.
We want this to be paired with a provided anonymous function like so:
“hello” bind GET – string that identifies the HTTP Method.
to {….} – the string that identifies the HTTP Method should execute this anon function.
First, let’s see a simpler example.
We created a much simpler infix function tofunction
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class A { fun test() { println("I am a test") } infix fun eat(food: Any) { println("I like to eat $food") } infix fun to (action: (name: String) -> String) { println("--- A is executing (to) another function! ----") println(action("Ricky")) } } fun main() { val a = A() a eat "sandiwches" a to { name: String -> "Welcome $name"} } |
Now we see how a to { name: String -> “Welcome $name”} can actually execute a function.
Hence, it is similar way in http4. Http4’s object have set up the string hello to GET using infix fun ‘bind’.
“hello” bind GET to {request:Request -> Reponse…}
Then it uses infix function ‘to’ to have it execute with an anonymous function.
Its using syntactic sugar to execute many functions together on 1 line.
abstract vs interface
ref – https://medium.com/@eneszor95/big-challenge-in-kotlin-interface-vs-abstract-class-520cc234e7c1
Abstract let’s other classes inherit from the abstract class.
Interface, let’s others conform to it.
Interfaces can have abstract methods
We provide a base abstract function. If you over-ride it in your class, it will call it. Otherwise, it will default down to the base abstract function in your interface.
In our example, addMilk is over-ridden. But we did not implement addCoffee, so it calls CoffeeMachineInterface’s addCoffee.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
interface CoffeeMachineInterface { // abstract method addMilk fun addMilk(){ println("CoffeeMachineInterface - Milk is adding...") } // abstract method addCoffee fun addCoffee(){ println("CoffeeMachineInterface - Coffee is being added...") } } class SuporCoffee : CoffeeMachineInterface { // we over-ride abstract method addMilk override fun addMilk() { println("SuporCoffee - Adds Foamy Milk") } // we didn't over-ride abstract method addCoffee, so it would execute the interface's abstract method } fun main(args: Array<String>) { println("Hello World!") val cup1 = SuporCoffee() cup1.addMilk() cup1.addCoffee() } |
output:
SuporCoffee – Adds Foamy Milk
CoffeeMachineInterface – Coffee is being added…
Interfaces can be properties…but need to be abstract
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
interface CoffeeMachineInterface { // you cannot initialize it. classes that conform to this interface must initialize it var coffeeAmount: Int } class SuporCoffee : CoffeeMachineInterface { override var coffeeAmount: Int = 0 get() { println("get() on 'coffeeAmount' for SuporCoffee") return field; } set(value) { println("set() on 'coffeeAmount' for SuporCoffee") field = value } } fun main(args: Array<String>) { println("Hello World!") val cup1 = SuporCoffee() cup1.coffeeAmount = 16 println("cup1 has ${cup1.coffeeAmount} oz of coffee") } |
output:
set() on ‘coffeeAmount’ for SuporCoffee
get() on ‘coffeeAmount’ for SuporCoffee
cup1 has 16 oz of coffee
An interface can be inherited from other interface.
abstract classes cannot be instantiated. You must derive from it, and then instantiate it.
1 2 3 4 5 6 7 8 9 10 |
abstract class Person { var age: Int = 40 fun displaySSN(ssn: Int) { println("My SSN is $ssn.") } abstract fun displayJob(description: String) } |
- an abstract class Person is created. You cannot create objects of the class.
- the class has a non-abstract property age and a non-abstract method displaySSN. IF you need to override these members in the subclass, they should be marked with open keyword.
- The class has an abstract method displayJob(). It doesn’t have any implementation and MUST be overridden in its subclasses.
So we create class Teacher and override displayJob. Then when we instantiate it, we can access its own implementation of displayJob and the abstract’s final base function displaySSN.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Teacher(name: String): Person(name) { override fun displayJob(description: String) { println(description) } } fun main(args: Array<String>) { val jack = Teacher("Jack Smith") jack.displayJob("I'm a mathematics teacher.") jack.displaySSN(23123) } |
Also, the practical side of abstract classes is that you can encapsulate a part of implementation that works with the state, so that it cannot be overridden in the derived classes.
Abstract properties can be initialized or abstract. When it is abstract, it cannot be initialized. It must be over-riden in the derived class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
abstract class CoffeeMachineController { // Property with initializer cannot be abstract. Thus, we declare abstract, it must NOT be initialized. // private as default abstract var coffeeAmount: Int? } class SuporCoffee: CoffeeMachineController() { override var coffeeAmount: Int? = 8 } fun main(args: Array<String>) { val cup1 = SuporCoffee() println("cup1 has ${cup1.coffeeAmount} oz of coffee"); } |
Property that are initialized must have keyword ‘open’
Abstract can hold state by using open and initializing a property. We then have the option of over-riding it in a derived class.
1 2 3 4 5 6 7 8 |
abstract class CoffeeMachineController { // Abstract can hold state like so. But we need use open. open means, it is override-able open val milkAmount: Int? = 0 } class SuporCoffee: CoffeeMachineController() { override val milkAmount: Int? = 8 } |
Getter Setter on a class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class FiatBravo { var price : String = "0" get() { println("get() on 'price' for FiatBravo") return field; } set(value) { println("set() on 'price' for FiatBravo") field = value } } val fiatBravo = FiatBravo() fiatBravo.price = "29689" println("FiatBravo priced at ${fiatBravo.price}") |
Property Delegation in Kotlin
Interface Delegation in Kotlin
ref – https://medium.com/@Joseph82/interface-delegation-in-kotlin-1404dfcd9167
Interface delegation, in Kotlin, is a simple yet powerful technique that allows a class to delegate the actual implementation of the interfaces, to separate objects.
Usually, we have a class implement a function which satisfies the required interface function like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// declare our interface interface EngineType { val type: String } // wants our component FiatBravo to conform to EngineType // we then override interface function 'type' class FiatBravo : EngineType { override val type: String get() = "Electric" } // we then create an instance of FiatBravo, and then get its engine type. val fiatBravo = FiatBravo() // Engine type: Electric println("Engine type: ${fiatBravo.type}") |
So our FiatBraov is directly implementing the abstract property defined in the interface EngineType.
But note that it’s 2020, and many other vehicles are probably going to use the Electric, instead of Diesel. We don’t want to have very car component re-write this Electric Engine type over and over.
Let’s use SteeringWheel as an example.
We want to create a SteeringWheel object that every car component just conform to it and use, and not have to implement it individually.
Let’s first create an interface like so:
1 2 3 4 |
interface SteeringControl { fun turnLeft() fun turnRight() } |
This tells us that whatever component conforms to this SteeringControl interface must at least implement turnLeft and turnRight.
We then create an object SteeringWheel and conform to SteeringControl. In our SteeringWheel component, we implement those abstract functions set by our interface SteeringControl.
1 2 3 4 5 6 7 8 9 |
object SteeringWheel : SteeringControl { override fun turnLeft() { println("Rotate steering wheel counterclockwise") } override fun turnRight() { println("Rotate steering wheel clockwise") } } |
Now, back in our FiatBravo, we must conform to interface SteeringControl because we’re a car. But we’d rather someone else do the implementation for us because we don’t want to be bothered with this. Thus, we use the by keyword to say that SteeringWheel is a component that conforms to SteeringControl, and we want it to implement it for us.
In other words, interface SteeringControls is to be implemented by SteeringWheel.
1 2 3 4 5 6 7 |
class FiatBravo : EngineType, SteeringControl by SteeringWheel { override val type: String get() = "Electric" } |
Now the output would be:
Turn handlebar to left
Turn handlebar to right
Engine type: Electric
So as you can see, we can over-ride abstract interface methods, and we can also have other components do it for us.
Finally, notice that our SteeringWheel is an object. We can also use a class to represent the blueprint of a component that does the implementing for us. And then just instantiate it
Let’s use Tires as an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
interface Tires { val brandName: String val circumference: Array<Int> } class GoodYear : Tires { override val circumference: Array<Int> get() = arrayOf<Int>(18, 22, 26) override val brandName: String get()="Good Year" } class FiatBravo : EngineType, SteeringControl by SteeringWheel, Tires by GoodYear() { override val type: String get() = "Electric" } fun main(args: Array<String>) { val fiatBravo = FiatBravo() fiatBravo.turnLeft() fiatBravo.turnRight() println("Engine type: ${fiatBravo.type}") } |