Understanding Class Hierarchies

Doyeon
7 min readNov 6, 2022

--

This chapter will discuss of object-oriented aspect of Kotlin as concept of class inheritance and explain how to define subclasses, interface and class delegation.

Inheritance

Most object-oriented languages use the concept of inheritance. When class A inherits class B, all instances of A are automatically considered instances of B. Which means, class A gets all members and extensions defined for B.

  • In Kotlin, classes support only single inheritance which means any class may not have more than one superclass. 단일 상속만을 지원한다. 즉, 어떤 클래스의 상위 클래스가 하나뿐이다.
  • Not specifying a superclass will automatically assumes that the class inherits from class Any. 상위클래스를 명시하지 않으면 컴파일러는 자동으로 내장 클래스인 Any 를 상속하는 것으로 가정한다.

Declaring a subclass

In Kotlin, there’s no special keywords like extends and implement as Java just add : after name of the class.

class Aircraft(val seats:Int): FlyingVehicle() {..}

  • open means open for inheritance, if there’s no open keyword before the class, it’s considered as final

open class FlyingVehicle: Vehicle() {..}

ad-hoc polymorphism is a powerful feature of inheritance, which allows you to provide different implementations of a superclass member for subclasses depending on the actual instance class at runtime by overriding a member of superclass. 상속이 제공하는 강력한 기능은 임의 다형성이다. 상위클래스 맴버의여러 다른 구현을 하위 클래스에서 제공하고, 런타임에 실제 인스턴스가 속한 클래스에 따라 구현을 한다. 이는 오버라이드해서 구현이 가능하다.

The Vehicle class provides common implementation of start() method, which is overridden by its inheritors — Car and Boat since the Vehicle class can be implement by the keyword “open” by its following function start() . however stop() is without open keyword then it cannot be overridable because its final.

  • final is default in kotlin, if not, use open keyowrd
  • when overriding methods, use override keyword.
  • when overridden, parameter must match its super class.
  • If declare the overridden member as final, it won’t be overridden further in subclasses.
  • properties can e overridden too.
  • protect : keyword is a special access modifier, which restricts members’ scope to its inheritors. 멤버의 영역을 하위 클래스의 영역으로만 제한하는 특별한 접근 변경자를 제공한다.

Subclass initialization

In Kotlin, Creating an instance of subclass, program also need to call the superclass’ initialization code first, since it may create an environment used by subclass code enforced automatically.

  • super keyword tells compiler that calling upper class.

Type Checking and casts

Since a variable of some class may refer to any instance of its subtypes at runtime, it’s useful to check its type and cast it to that type when necessary. 런타임에는 해당 클래스의 하위 타입 중에서 아무 타입의 객체나 가리킬 수 있으므로, 어떤 타입에 속하는지 검사하고 타입을 변환해야한다.

  • is : an operator returns true, !is as opposite

obj is Int , obj !is Int

  • is similar to Java’s instaceof but, it also can check null type unlike java.

null is String?

  • is / !is checks and smart cast are also supported but smart cast only be allowed when the variable type does not change before its check and the usage by compiler. 스마트 캐스트도 지원이 된다. 하지만, 컴파일러는 검사 시점과 사용 시점 사이에 변수가 변경되지 않는다고 확신 할 수 있을 때만 스마트 캐스트를 허용한다.

Smart cast not allowed:

  • properties and variables with custom accessors because the value can be change later.
  • open member properties since they can be overridden in subtypes and can be given a custom accessor.
  • Mutable local variables when their value is explicitly changed between the check and read.

when smartcasts are not available, we can use explicit operators to coerce a given value to some type using as and as?

Common methods

Any class is a root of the Kotlin class hierarchy. When you don’t specify super class, compiler automatically assumes that it’s Any.

  • Structural equality (==and !=) 구조적 동등성
  • Computation of hash code, which is used by some collection types, like HashSet or HashMap 해시 코드 계산
  • Default conversion to String 스트링으로 변환하는 기본적인 방법

Abstract classes and interface

Abstract classes and members

Abstract class cannot be instantiated directly but instead serve only as super types for other classes with abstract keyword. 직접 인스턴스화할수 없고, 다른 클래스의 상의 클래스 역활만 할 수 있다.

The difference between abstract and non abstract is can only be called from the subclass. Also members and methods should also be abstract too like no implementations. 추상클래스와 비추상클래스의 차이는 추상클래스는 오직 하위 클래스에서 호출로만 불리고, 추상클래스의 추상 멤버, 메서드 등을 구현할 수있다는것이다. [not finished]

since abstract members are not supposed to have an implementation,

  • abstract properties may not have initializers, explicit accessors or by clause 추상 프로퍼티를 초기화할 수 없고, 명시적인 접근자나 by 절을 추가할 수 없다.
  • abstract functions may bot have a body 추상함수에는 본문이 없어야 한다.
  • Both abstract properties and functions must explicitly specify their return type, since it cannot be inferred automatically. 추론이 불가능 하기 때문에 반환타입을 적어야한다.

abstract members are open so no need to mark them as open

Interfaces

interface is a type that contain methods and properties. (both abstract and non abstract) but can’t define either instance state or constructors by using keyword interface. 인터페이스 키워드를 붙여야하고 메서드나 프로퍼티를 포함하지만, 자체적인 인스턴스 상태나 생성자를 만들수 없다.

  • members are abstract by default. 디폴트가 추상 멤버다.
  • can be supertypes for classes and other interfaces. 다른 인터페이스나 클래스의 상위 타입이 될 수 있다.
  • when a non-abstract class inherits an interface, it must provide implementations for all abstract members with override keyword. 비추상 클래스가 상속할 때에는 모든 추상 멤버에 대한 구현을 제공해야고 override 키워드를 붙여야한다.
  • not allowed to inherit from classes except for Any since it’s implicitly inherited by each class and interface. 인터페이스가 다른 클래스를 상속할 수는 없다. 단, Any클래스는 예외이다.
  • Interface functions and properties may also have implementations.
  • marking an interface member as final cause an error, but using extension functions and properties act as final.
fun Vehicle.relativeSpeed(vehicle: Vehicle) = currentSpeed - vehicle.currentSpeed
  • not allowed to define state and they can’t contain properties with backing fields.
  • unlike abstract class, having constructor is forbidden.
  • multiple inheritance is allowed, but when the multiple interfaces are merged, need to use extended form of super qualified with a supertype name:
class Amphibia : Car, Ship {
override fun move() {
super<Car>.move()
super<Ship>.move()
}
}

Sealed classes

Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance. like it’s not possible to know which subclasses are inherit from one class and how many are but using sealed classes, compiler will know which classes are inherited.

For example, if we have code below to check the status then,

abstract class ResultState {
class Success(val value: Any): ResultState()
class Error(val message: String): ResultState()
}

code should always contain every state but if it’s missing even one state, it will cause an error saying using else branch when using when statement.

fun getStateMessage(state: ResultState): String {return when (state) {   is Success -> "Success"   else -> "No State"
}
}

but using sealed class, it will prevent from this error above since it knows what the subclasses are. That’s why need to list down all the states from ResultState if not, it will cause an error.

sealed class Result {
class Success(val value: Any) : Result() {...}
class Error(val message: String) : Result() {...}
}
  • When the class is marked as sealed, its inheritors may be declared in either its body as nested classes and objects, or as top-level classes in the same file. 이 클래스를 상속하는 클래스는 내포된 클래스 또는 객체로 정의되거나 같은 파일 안에서 최상위 클래스로 정의돼야만 한다.
  • Outside these scope, sealed class is final and cannot be inherited from. 외부에서는 final 과 같은 효과를 지니며 상속할수없다.
  • sealed class is also abstract so can’t create its instance directly. 추상클래스이며 직접적으로 인스턴스를 만들수 없다.
  • sealed class constructors are private by default.
  • support the exhaustive form of when expression that allows us to avoid the redundant else branch.
  • sealed class’ sub class may have their own inheritors not the directly to the sealed class. 하위 클래스는 상속이 있을수도 있다.
  • sealed classes may extend other classes as well. 다른 클래스를 상속할 수 있다.
  • possible to use data class. 데이터 클래스 사용이 가능하다.
  • sealed class implementation may also be an object. it behaves like enum. 객체로도 구현 할 수 있다.

Delegation

Delegation is the assignment of authority from one instance to another.

Kotlin classes are final by default to prevent from accidental inheritance from classes that are not supposed to have. But what if we still need to extend or change the behavior from the class that cannot inherit from? We can use composition(has-a) which means having instances in class but when the methods gets more and more, we also need to write every wrapper methods.

interface IWindow {
fun getWidth() : Int
fun getHeight() : Int
}

open class TransparentWindow : IWindow {
override fun getWidth(): Int {
return 100
}

override fun getHeight() : Int{
return 150
}
}

class UI(window: IWindow) : IWindow {
val mWindow: IWindow = window

override fun getWidth(): Int {
return mWindow.getWidth()
}

override fun getHeight(): Int {
return mWindow.getHeight()
}
}
fun main(args: Array<String>) {
val window: IWindow = TransparentWindow()
val ui = UI(window)
System.out.println("Width : ${ui.getWidth()}, height: ${ui.getHeight()}")
}

However using delegate pattern, we can prevent from this problem using by .

interface IWindow {
fun getWidth() : Int
fun getHeight() : Int
}

open class TransparentWindow() : IWindow {
override fun getWidth(): Int {
return 100
}

override fun getHeight() : Int{
return 150
}
}

class UI(window: IWindow) : IWindow by window {

}
fun main(args: Array<String>) {
val window: IWindow = TransparentWindow()
val ui = UI(window)
System.out.println("Width : ${ui.getWidth()}, height: ${ui.getHeight()}")
}

--

--

Doyeon

Passionate iOS developer creating extraordinary apps. Innovative, elegant, and collaborative. Constantly pushing boundaries.Let's build the future together📱✨🚀