Kotlin Programming 2022 Spring - Lecture 9
===
###### tags: `Kotlin 2022 Spring` `Kotlin` `2022 Spring`
This lecture intends to cover the materials in Chapter 14 and Chapter 15.
## Inheritance
We will use Easy Card as an example to show the inhertance of classes in Kotlin.
```kotlin
class EasyCard(balance: Int) {
var balance = balance
private set(value) {
field = value
}
fun pay(amount: Int): Boolean {
require(amount > 0) { "amount should be positive" }
if (balance >= amount) {
balance -= amount
return true
}
return false
}
fun charge(amount: Int): Boolean {
require(amount > 0) { "amount should be positive" }
if (balance + amount > 10_000) return false
balance += amount
return true
}
}
fun main() {
val ezcard = EasyCard(100)
println("The balance is ${ezcard.balance}.")
var res = ezcard.pay(50)
println("Pay 50: ${if (res) "successful" else "failed"}")
println("The balance is ${ezcard.balance}.")
res = ezcard.pay(60)
println("Pay 60: ${if (res) "successful" else "failed"}")
println("The balance is ${ezcard.balance}.")
res = ezcard.charge(5000)
println("Charge 5000: ${if (res) "successful" else "failed"}")
println("The balance is ${ezcard.balance}.")
res = ezcard.charge(6000)
println("Charge 6000: ${if (res) "successful" else "failed"}")
println("The balance is ${ezcard.balance}.")
// will not compile
// ezcard.balance += 100
}
```
### Subclass: a Special Case of its Superclass
Named Easy Cards is a kind of easy cards.
+ Keywords in this sample code: `open`, `as`
```kotlin
// only open class can be inherited.
open class EasyCard(balance: Int) {
var balance = balance
private set(value) {
field = value
}
fun pay(amount: Int): Boolean {
require(amount > 0) { "amount should be positive" }
if (balance >= amount) {
balance -= amount
return true
}
return false
}
fun charge(amount: Int): Boolean {
require(amount > 0) { "amount should be positive" }
if (balance + amount > 10_000) return false
balance += amount
return true
}
}
// adding a new value "holder" for the name of the card holder
class NamedEasyCard(balance: Int, val holder: String): EasyCard(balance)
fun main() {
val ezcard: EasyCard = NamedEasyCard(100, "MZ")
var res = ezcard.pay(100)
println("ezcard pay 100: ${if (res) "successful" else "failed"}")
res = ezcard.charge(100)
println("ezcard charge 100: ${if (res) "successful" else "failed"}")
// failed to compile
// println("ezcard holder: ${ezcard.holder}")
// use of "as"
println("ezcard holder: ${(ezcard as NamedEasyCard).holder}")
val namedCard: NamedEasyCard = NamedEasyCard(100, "truckski")
res = namedCard.pay(100)
println("namedCard pay 100: ${if (res) "successful" else "failed"}")
res = namedCard.charge(100)
println("namedCard charge 100: ${if (res) "successful" else "failed"}")
println("namedCard holder: ${namedCard.holder}")
}
```
## Override
+ Keywords: `override`, `super`, `protected`, `final`
+ `override` re-define the `open` properties and the `open` functions in the superclass.
+ Use `super` to access the properties and the functions in the superclass.
+ Use `protected` to allow subclasses to access the properties and the functions. (Instead of `private`)
+ Use `final` to un-`open` the properties and the functions for further `override`.
```kotlin=
// only open class can be inherited.
open class EasyCard(balance: Int) {
var balance = balance
// to allow only EasyCard to modify the property, use private
// to allow subclass to modify the property, use protected
// to allow all to modify the property, use public (default)
private set(value) {
field = value
}
protected open val capacity = 10_000
// only open function can be replaced in subclasses
open fun pay(amount: Int): Boolean {
require(amount > 0) { "amount should be positive" }
if (balance >= amount) {
balance -= amount
return true
}
return false
}
open fun charge(amount: Int): Boolean {
require(amount > 0) { "amount should be positive" }
if (balance + amount > capacity) return false
balance += amount
return true
}
}
// adding a new value "holder" for the name of the card holder
open class NamedEasyCard(balance: Int, val holder: String) : EasyCard(balance)
class StudentCard(holder: String, val school: String, val studentID: String) : NamedEasyCard(0, holder) {
// rewrite the value in the super class
final override val capacity = 3_000
private var isActive = true
fun activate() {
isActive = true
}
fun deactivate() {
isActive = false
}
override fun pay(amount: Int): Boolean {
// short-cut evaluation: when isActive is false, super.pay(amount) won't be executed.
return isActive && super.pay(amount)
}
override fun charge(amount: Int): Boolean {
// short-cut evaluation: when isActive is false, super.charge(amount) won't be executed.
return isActive && super.charge(amount)
}
}
class EasyCreditCard(holder: String) : NamedEasyCard(0, holder) {
// rewrite the value in the super class
override val capacity = 30_000
// rewrite the function in the super class
final override fun pay(amount: Int): Boolean {
require(amount > 0) { "amount should be positive" }
if (amount > capacity) return false
while (balance < amount) {
if (!autoload()) return false
}
// use super.pay to use the pay function in the superclass
return super.pay(amount)
// return (this as EasyCard).pay(amount)
}
fun autoload(): Boolean {
return charge(minOf(500, capacity - balance))
}
}
fun main() {
var res: Boolean
val studentCard = StudentCard("MZ", "NCTU", "8817106").apply {
println("The student card holder $holder is $studentID from $school")
println("Balance: $balance")
}
res = studentCard.pay(100)
println("studentCard pays 100: ${if (res) "successful" else "failed"}")
println("studentCard.balance: ${studentCard.balance}")
res = studentCard.pay(12345)
println("studentCard pays 12345: ${if (res) "successful" else "failed"}")
println("studentCard.balance: ${studentCard.balance}")
res = studentCard.charge(100)
println("studentCard charges 100: ${if (res) "successful" else "failed"}")
println("studentCard.balance: ${studentCard.balance}")
studentCard.deactivate()
res = studentCard.charge(100)
println("studentCard charges 100: ${if (res) "successful" else "failed"}")
println("studentCard.balance: ${studentCard.balance}")
val creditcard = EasyCreditCard("MZ")
println("creditcard.balance: ${creditcard.balance}")
res = creditcard.pay(100)
println("creditcard pay 100: ${if (res) "successful" else "failed"}")
println("creditcard.balance: ${creditcard.balance}")
res = creditcard.pay(12345)
println("creditcard pay 12345: ${if (res) "successful" else "failed"}")
println("creditcard.balance: ${creditcard.balance}")
res = (creditcard as EasyCard).charge(15000)
println("creditcard charge 15000: ${if (res) "successful" else "failed"}")
println("creditcard.balance: ${creditcard.balance}")
res = (creditcard as EasyCard).pay(20000)
println("creditcard pays 20000 as an EasyCard: ${if (res) "successful" else "failed"}")
println("creditcard.balance: ${creditcard.balance}")
val easyCard: EasyCard = EasyCreditCard("truckski")
println("Balance: ${easyCard.balance}")
res = easyCard.pay(12345)
println("easyCard pays 12345: ${if (res) "successful" else "failed"}")
println("Balance: ${easyCard.balance}")
}
```
## Type Checking and Casting
+ Kotlin class hierarchy
```graphviz
digraph hierarchy {
nodesep=1.0 // increases the separation between nodes
node [color=Black,fontname=Courier,shape=box] //All nodes will this shape and colour
edge [color=Black, style=dashed] //All the lines look like this
Any->{EasyCard}
EasyCard->{NamedEasyCard}
NamedEasyCard->{StudentCard EasyCreditCard}
}
```
+ `is` operator
```kotlin
// only open class can be inherited.
open class ComplexNumber(val real: Double,val im: Double)
open class RealNumber(real: Double) : ComplexNumber(real, 0.0)
open class Integer(val value: Int) : RealNumber(value.toDouble())
open class Odd(value: Int) : Integer(value) {
init {
require(value % 2 != 0) {"value must be odd!"}
}
}
open class Even(value: Int) : Integer(value) {
init {
require(value % 2 == 0) {"value must be even!"}
}
}
class One : Odd(1)
class Zero : Even(0)
fun test(identifier: String, x: Any) {
println("Testing $identifier...")
println("$identifier is Any: ${x is Any}")
println("$identifier is ComplexNumber: ${x is ComplexNumber}")
println("$identifier is RealNumber: ${x is RealNumber}")
println("$identifier is Integer: ${x is Integer}")
println("$identifier is Odd: ${x is Odd}")
println("$identifier is Even: ${x is Even}")
println("$identifier is One: ${x is One}")
println("$identifier is Zero: ${x is Zero}")
}
fun main() {
val one = One()
val zero = Zero()
test("one", one)
test("zero", zero)
val integer = RealNumber(1.toDouble())
test("integer", integer)
}
```
+ Use `as` to cast an identifier to a certain type
+ Can cause `ClassCastException`
+ Smart Cast: If after use `is` operator and `if`/`when`, the compiler can derive the type of a value / variable. Then, Kotlin will treat the value / variable as that type of data.
```kotlin
fun main() {
val list = listOf(1.0, "add", 2.0, "time", 3.0, "minus", 4.0)
for (any in list) {
when (any) {
is String -> {
println("$any is a String and has length ${any.length}.")
// failed to compile: any is a String and does not support operator times (*)
// println("$any is a Double, and its square is ${any * any}")
}
is Double -> {
// failed to compile: any is a Double and does not have property length
// println("$any is a String and has length ${any.length}.")
println("$any is a Double, and its square is ${any * any}")
}
else -> {
println("$any is neither a String nor a Double.")
}
}
}
}
```
## `object` keyword
### Object declaraions
Similar to `class`, but there will be only one instance can be accessed by its name. For object defined in this manner, Kotlin uses lazy initialization, i.e., initializes when accessed for the first time.
### Object expression
To create an anonymous class. Object expressions are executed and initialized immediately.
```kotlin
fun main() {
val list = listOf(1, 3, 5, -2, -4, -6)
println(list.sortedWith(object : Comparator<Int> {
override fun compare(x: Int, y: Int): Int {
val absX = if (x < 0) -x else x
val absY = if (y < 0) -y else y
return absX - absY
}
}))
}
```
### Companion objects
To create some properties and functions that belong to the class (not the instance). Companion objects are initialized when we load the class to JVM.
```kotlin
class Student(val name: String, val id: String) {
companion object {
var population = 0
}
init {
population += 1
}
}
fun main() {
val bachelor = Student("MZ", "8817106")
val master = Student("MZMZ", "9217550")
val doctor = Student("MZMZMZ", "9317813")
println("Population: ${Student.population}")
}
```
## Enumerated class
```kotlin
enum class Direction {
EAST, WEST, NORTH, SOUTH
}
fun clockwise(d: Direction) = when (d) {
Direction.EAST -> Direction.SOUTH
Direction.WEST -> Direction.NORTH
Direction.SOUTH -> Direction.WEST
Direction.NORTH -> Direction.EAST
}
fun main() {
var direction = Direction.EAST
println("Current direction: $direction")
println("Turing 90 degrees clockwise.")
direction = clockwise(direction)
println("Current direction: $direction")
println("Turing 90 degrees clockwise.")
direction = clockwise(direction)
println("Current direction: $direction")
println("Turing 90 degrees clockwise.")
direction = clockwise(direction)
println("Current direction: $direction")
println("Turing 90 degrees clockwise.")
direction = clockwise(direction)
println("Current direction: $direction")
}
```
## Data class
Provide the following for "free".
+ `.equals()` and `.hashCode()`
+ `toString()`
+ `componentN()` and destrcturing declaration
+ `copy()`
```kotlin
class Student(val name: String, var id: String)
data class StudentData(val name: String, var id: String)
fun main() {
val mzNonData = Student("MZ", "8817106")
println("mzNonData == Student(\"MZ\",\"8817106\"): ${mzNonData == Student("MZ", "8817106")}")
println("mzNonData: $mzNonData")
// won't compile
// val (name, id) = mzNonData
val mzData = StudentData("MZ", "8817106")
println("mzData == StudentData(\"MZ\",\"8817106\"): ${mzData == StudentData("MZ", "8817106")}")
println("mzData: $mzData")
val (name, id) = mzData
println("name: $name, id: $id")
val another = mzData
val copy = mzData.copy()
mzData.id = "9217550"
println("mzData: $mzData")
println("another: $another")
println("copy: $copy")
}
```
## Operator overloading
Kotlin supports operator overloading: you may create your own class that supports arithmetic operators and more.
```kotlin
data class Complex(val real: Double, val im: Double) {
operator fun plus(other: Complex) = Complex(real + other.real, im + other.im)
operator fun minus(other: Complex) = Complex(real - other.real, im - other.im)
operator fun times(other: Complex) = Complex(real * other.real - im * other.im, real * other.im + im * other.real)
operator fun div(other: Complex) = (other.real * other.real + other.im * other.im).let {
Complex((real * other.real - im * -other.im) / it, (real * -other.im + im * other.real) / it)
}
}
fun main() {
val a = Complex(3.0, 4.0)
val b = Complex(4.0, -3.0)
println(a + b)
println(a - b)
println(a * b)
println(a / b)
println(a / b / a * b)
}
```
See the [official document](https://kotlinlang.org/docs/reference/operator-overloading.html) for more information.