This material was written by Aasmund Eldhuset; it is owned by Khan Academy and is licensed for use under CC BY-NC-SA 3.0 US. Please note that this is not a part of Khan Academy's official product offering.
Kotlin supports single-parent class inheritance - so each class (except the root class Any
) has got exactly one parent class, called a superclass. Kotlin wants you to think through your class design to make sure that it's actually safe to subclass it, so classes are closed by default and can't be inherited from unless you explicitly declare the class to be open or abstract. You can then subclass from that class by declaring a new class which mentions its parent class after a colon:
open class MotorVehicle
class Car : MotorVehicle()
Classes that don't declare a superclass implicitly inherit from Any
. The subclass must invoke one of the constructors of the base class, passing either parameters from its own constructor or constant values:
open class MotorVehicle(val maxSpeed: Double, val horsepowers: Int)
class Car(
val seatCount: Int,
maxSpeed: Double
) : MotorVehicle(maxSpeed, 100)
The subclass inherits all members that exist in its superclass - both those that are directly defined in the superclass and the ones that the superclass itself has inherited. In this example, Car
contains the following members:
seatCount
, which isCar
's own propertymaxSpeed
andhorsepowers
, which are inherited fromMotorVehicle
toString()
,equals()
, andhashCode()
, which are inherited fromAny
Note that the terms "subclass" and "superclass" can span multiple levels of inheritance - Car
is a subclass of Any
, and Any
is the superclass of everything. If we want to restrict ourselves to one level of inheritance, we will say "direct subclass" or "direct superclass".
Note that we do not use val
in front of maxSpeed
in Car
- doing so would have introduced a distinct property in Car
that would have shadowed the one inherited from MotorVehicle
. As written, it's just a constructor parameter that we pass on to the superconstructor.
private
members (and internal
members from superclasses in other modules) are also inherited, but are not directly accessible: if the superclass contains a private property foo
that is referenced by a public function bar()
, instances of the subclass will contain a foo
; they can't use it directly, but they are allowed to call bar()
.
When an instance of a subclass is constructed, the superclass "part" is constructed first (via the superclass constructor). This means that during execution of the constructor of an open class, it could be that the object being constructed is an instance of a subclass, in which case the subclass-specific properties have not been initialized yet. For that reason, calling an open function from a constructor is risky: it might be overridden in the subclass, and if it is accessing subclass-specific properties, those won't be initialized yet.
If a member function or property is declared as open
, subclasses may override it by providing a new implementation. Let's say that MotorVehicle
declares this function:
open fun drive() =
"$horsepowers HP motor vehicle driving at $maxSpeed MPH"
If Car
does nothing, it will inherit this function as-is, and it will return a message with the car's horsepowers and max speed. If we want a car-specific message, Car
can override the function by redeclaring it with the override
keyword:
override fun drive() =
"$seatCount-seat car driving at $maxSpeed MPH"
The signature of the overriding function must exactly match the overridden one, except that the return type in the overriding function may be a subtype of the return type of the overridden function.
If what the overriding function wants to do is an extension of what the overridden function did, you can call the overridden function via super
(either before, after, or between other code):
override fun drive() =
super.drive() + " with $seatCount seats"
The single-parent rule often becomes too limiting, as you'll often find commonalities between classes in different branches of a class hierarchy. These commonalities can be expressed in interfaces.
An interface is essentially a contract that a class may choose to sign; if it does, the class is obliged to provide implementations of the properties and functions of the interface. However, an interface may (but typically doesn't) provide a default implementation of some or all of its properties and functions. If a property or function has a default implementation, the class may choose to override it, but it doesn't have to. Here's an interface without any default implementations:
interface Driveable {
val maxSpeed: Double
fun drive(): String
}
We can choose to let MotorVehicle
implement that interface, since it's got the required members - but now we need to mark those members with override
, and we can remove open
since an overridden function is implicitly open:
open class MotorVehicle(
override val maxSpeed: Double,
val wheelCount: Int
) : Driveable {
override fun drive() = "Wroom!"
}
If we were to introduce another class Bicycle
, which should be neither a subclass nor a superclass of MotorVehicle
, we could still make it implement Driveable
, as long as we declare maxSpeed
and drive
in Bicycle
.
Subclasses of a class that implements an interface (in this case, Car
) are also considered to be implementing the interface.
A symbol that is declared inside an interface normally should be public. The only other legal visibility modifier is private
, which can only be used if the function body is supplied - that function may then be called by each class that implements the interface, but not by anyone else.
As for why you would want to create an interface, other than as a reminder to have your classes implement certain members, see the section on polymorphism.
Some superclasses are very useful as a grouping mechanism for related classes and for providing shared functions, but are so general that they're not useful on their own. MotorVehicle
seems to fit this description. Such a class should be declared abstract, which will prevent the class from being instantiated directly:
abstract class MotorVehicle(val maxSpeed: Double, val wheelCount: Int)
Now, you can no longer say val mv = MotorVehicle(100, 4)
.
Abstract classes are implicitly open, since they are useless if they don't have any concrete subclasses.
When an abstract class implements one or more interfaces, it is not required to provide definitions of the members of its interfaces (but it can if it wants to). It must still declare such members, using abstract override
and not providing any body for the function or property:
abstract override val foo: String
abstract override fun bar(): Int
Being abstract is the only way to "escape" from having to implement the members of your interfaces, by offloading the work onto your subclasses - if a subclass wants to be concrete, it must implement all the "missing" members.
Polymorphism is the ability to treat objects with similar traits in a common way. In Python, this is achieved via ducktyping: if x
refers to some object, you can call x.quack()
as long as the object happens to have the function quack()
- nothing else needs to be known (or rather, assumed) about the object. That's very flexible, but also risky: if x
is a parameter, every caller of your function must be aware that the object they pass to it must have quack()
, and if someone gets it wrong, the program blows up at runtime.
In Kotlin, polymorphism is achieved via the class hierarchy, in such a way that it is impossible to run into a situation where a property or function is missing. The basic rule is that a variable/property/parameter whose declared type is A
may refer to an instance of a class B
if and only if B
is a subtype of A
. This means that either, A
must be a class and B
must be a subclass of A
, or that A
must be an interface and B
must be a class that implements that interface or be a subclass of a class that does. With our classes and interfaces from the previous sections, we can define these functions:
fun boast(mv: MotorVehicle) =
"My ${mv.wheelCount} wheel vehicle can drive at ${mv.maxSpeed} MPH!"
fun ride(d: Driveable) =
"I'm riding my ${d.drive()}"
and call them like this:
val car = Car(4, 120)
boast(car)
ride(car)
We're allowed to pass a Car
to boast()
because Car
is a subclass of MotorVehicle
. We're allowed to pass a Car
to ride()
because Car
implements Driveable
(thanks to being a subclass MotorVehicle
). Inside boast()
, we're only allowed to access the members of the declared parameter type MotorVehicle
, even if we're in a situation where we know that it's really a Car
(because there could be other callers that pass a non-Car
). Inside ride()
, we're only allowed to access the members of the declared parameter type Driveable
. This ensures that every member lookup is safe - the compiler only allows you to pass objects that are guaranteed to have the necessary members. The downside is that you will sometimes be forced to declare "unnecessary" interfaces or wrapper classes in order to make a function accept instances of different classes.
With collections and functions, polymorphism becomes more complicated - see the section on generics.
When you take an interface or an open class as a parameter, you generally don't know the real type of the parameter at runtime, since it could be an instance of a subclass or of any class that implements the interface. It is possible to check what the exact type is, but like in Python, you should generally avoid it and instead design your class hierarchy such that you can do what you need by proper overriding of functions or properties.
If there's no nice way around it, and you need to take special actions based on what type something is or to access functions/properties that only exist on some classes, you can use is
to check if the real type of an object is a particular class or a subclass thereof (or an implementor of an interface). When this is used as the condition in an if
, the compiler will let you perform type-specific operations on the object inside the if
body:
fun foo(x: Any) {
if (x is Person) {
println("${x.name}") // This wouldn't compile outside the if
}
}
If you want to check for not being an instance of a type, use !is
. Note that null
is never an instance of any non-nullable type, but it is always an "instance" of any nullable type (even though it technically isn't an instance, but an absence of any instance).
The compiler will not let you perform checks that can't possibly succeed because the declared type of the variable is a class that is on an unrelated branch of the class hierarchy from the class you're checking against - if the declared type of x
is MotorVehicle
, you can't check if x
is a Person
. If the right-hand side of is
is an interface, Kotlin will allow the type of the left-hand side to be any interface or open class, because it could be that some subclass thereof implements the interface.
If your code is too clever for the compiler, and you know without the help of is
that x
is an instance of Person
but the compiler doesn't, you can cast your value with as
:
val p = x as Person
This will raise a ClassCastException
if the object is not actually an instance of Person
or any of its subclasses. If you're not sure what x
is, but you're happy to get null if it's not a Person
, you can use as?
, which will return null if the cast fails. Note that the resulting type is Person?
:
val p = x as? Person
You can also use as
to cast to a nullable type. The difference between this and the previous as?
cast is that this one will fail if x
is a non-null instance of another type than Person
:
val p = x as Person?
If you find that an interface that you want a class to implement is already implemented by one of the properties of the class, you can delegate the implementation of that interface to that property with by
:
interface PowerSource {
val horsepowers: Int
}
class Engine(override val horsepowers: Int) : PowerSource
open class MotorVehicle(val engine: Engine): PowerSource by engine
This will automatically implement all the interface members of PowerSource
in MotorVehicle
by invoking the same member on engine
. This only works for properties that are declared in the constructor.
Let's say that you're writing a simple ORM. Your database library represents a row as instances of a class Entity
, with functions like getString("name")
and getLong("age")
for getting typed values from the given columns. We could create a typed wrapper class like this:
abstract class DbModel(val entity: Entity)
class Person(val entity: Entity) : DbModel(entity) {
val name = entity.getString("name")
val age = entity.getLong("age")
}
That was easy, but maybe we'd want to do lazy-loading so that we won't spend time on extracting the fields that won't be used (especially if some of them contain a lot of data in a format that it is time-consuming to parse), and maybe we'd like support for default values. While we could implement that logic in a get()
block, it would need to be duplicated in every property. Alternatively, we could implement the logic in a separate StringProperty
class (note that this simple example is not thread-safe):
class StringProperty(
private val model: DbModel,
private val fieldName: String,
private val defaultValue: String? = null
) {
private var _value: String? = defaultValue
private var loaded = false
val value: String?
get() {
// Warning: This is not thread-safe!
if (loaded) return _value
if (model.entity.contains(fieldName)) {
_value = model.entity.getString(fieldName)
}
loaded = true
return _value
}
}
// In Person
val name = StringProperty(this, "name", "Unknown Name")
Unfortunately, using this would require us to type p.name.value
every time we wanted to use the property. We could do the following, but that's also not great since it introduces an extra property:
// In Person
private val _name = StringProperty(this, "name", "Unknown Name")
val name get() = _name.value
The solution is a delegated property, which allows you to specify the behavior of getting and setting a property (somewhat similar to implementing __getattribute__()
and __setattribute__()
in Python, but for one property at a time).
class DelegatedStringProperty(
private val fieldName: String,
private val defaultValue: String? = null
) {
private var _value: String? = null
private var loaded = false
operator fun getValue(thisRef: DbModel, property: KProperty<*>): String? {
if (loaded) return _value
if (thisRef.entity.contains(fieldName)) {
_value = thisRef.entity.getString(fieldName)
}
loaded = true
return _value
}
}
The delegated property can be used like this to declare a property in Person
- note the use of by
instead of =
:
val name by DelegatedStringProperty(this, "name", "Unknown Name")
Now, whenever anyone reads p.name
, getValue()
will be invoked with p
as thisRef
and metadata about the name
property as property
. Since thisRef
is a DbModel
, this delegated property can only be used inside DbModel
and its subclasses.
A nice built-in delegated property is lazy
, which is a properly threadsafe implementation of the lazy loading pattern. The supplied lambda expression will only be evaluated once, the first time the property is accessed.
val name: String? by lazy {
if (thisRef.entity.contains(fieldName)) {
thisRef.entity.getString(fieldName)
} else null
}
If you want to restrict the set of subclasses of a base class, you can declare the base class to be sealed
(which also makes it abstract), in which case you can only declare subclasses in the same file. The compiler then knows the complete set of possible subclasses, which will let you do exhaustive when
expression for all the possible subtypes without the need for an else
clause (and if you add another subclass in the future and forget to update the when
, the compiler will let you know).
← Previous: Visibility modifiers | Next: Objects and companion objects →