17
Interfaces
Written by Victoria Gonda
Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as
text.You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.
You’ve learned about two Kotlin custom types: Classes and objects. There’s another custom type that’s quite useful: Interfaces.
Unlike the other custom types, interfaces aren’t anything you instantiate directly. Instead, they define a blueprint of behavior that concrete types conform to. With an interface, you define a common set of properties and behaviors that concrete types go and implement. The primary difference between interfaces and other custom types is that interfaces themselves cannot contain state.
In this chapter, you’ll learn about interfaces and see why they’re central to programming in Kotlin. Open up a Kotlin file to get going.
Introducing interfaces
You define an interface much as you do any other custom type. Add this interface to your Kotlin file, outside the main()
function:
interface Vehicle {
fun accelerate()
fun stop()
}
This creates a Vehicle
interface with blueprint instructions for two methods: accelerate()
and stop()
.
The keyword interface
is followed by the name of the interface, followed by curly braces with the members of the interface inside. The big difference you’ll notice is that the interface doesn’t have to contain any implementation.
That means you can’t instantiate a Vehicle
directly:
Instead, you use interfaces to enforce methods and properties on other types. What you’ve defined here is something like the idea of a vehicle — it’s something that can accelerate and stop.
Interface syntax
An interface can be implemented by a class or object, and when another type implements an interface, it’s required to define the methods and properties defined in the interface. Once a type implements all members of an interface, the type is said to conform to the interface.
class Unicycle: Vehicle {
var peddling = false
override fun accelerate() {
peddling = true
}
override fun stop() {
peddling = false
}
}
Methods in interfaces
In the Vehicle
interface above, you define a pair of methods, accelerate()
and stop()
, that all types conforming to Vehicle
must implement.
enum class Direction {
LEFT, RIGHT
}
interface DirectionalVehicle {
fun accelerate()
fun stop()
fun turn(direction: Direction)
fun description(): String
}
interface OptionalDirectionalVehicle {
fun turn(direction: Direction = Direction.LEFT)
}
class OptionalDirection: OptionalDirectionalVehicle {
override fun turn(direction: Direction) {
println(direction)
}
}
val car = OptionalDirection()
car.turn() // > LEFT
car.turn(Direction.RIGHT) // > RIGHT
Default method implementations
Just as you can in Java 8, you can define default implementations for the methods in an interface.
interface SpaceVehicle {
fun accelerate()
// 1
fun stop() {
println("Whoa, slow down!")
}
}
class LightFreighter: SpaceVehicle {
// 2
override fun accelerate() {
println("Proceed to hyperspace!")
}
}
val falcon = LightFreighter()
falcon.accelerate() // > Proceed to hyperspace!
falcon.stop() // > "Whoa, slow down!
class Starship: SpaceVehicle {
override fun accelerate() {
println("Warp factor 9 please!")
}
override fun stop() {
super.stop()
println("That kind of hurt!")
}
}
val enterprise = Starship()
enterprise.accelerate()
// > Warp factor 9 please!
enterprise.stop()
// > Whoa, slow down!
// > That kind of hurt!"
Properties in interfaces
You can also define properties in an interface. Add this interface:
interface VehicleProperties {
val weight: Int // abstract
val name: String
get() = "Vehicle"
}
class Car: VehicleProperties {
override val weight: Int = 1000
}
class Tank: VehicleProperties {
override val weight: Int
get() = 10000
override val name: String
get() = "Tank"
}
Interface inheritance
The Vehicle
interface contains a set of methods that could apply to any type of vehicle, such as a bike, a car, a snowmobile or even an airplane!
interface WheeledVehicle: Vehicle {
val numberOfWheels: Int
var wheelSize: Double
}
class Bike: WheeledVehicle {
var peddling = false
var brakesApplied = false
override val numberOfWheels = 2
override var wheelSize = 622.0
override fun accelerate() {
peddling = true
brakesApplied = false
}
override fun stop() {
peddling = false
brakesApplied = true
}
}
Mini-exercises
- Create an interface
Area
that defines a read-only propertyarea
of typeDouble
. - Implement
Area
with classes representingSquare
,Triangle
, andCircle
. - Add a circle, a square, and a triangle to an array. Convert the array of shapes to an array of areas using
map
.
Implementing multiple interfaces
One class can only inherit from another single class. This is the property of single inheritance. In contrast, a class can adopt as many interfaces as you’d like!
interface Wheeled {
val numberOfWheels: Int
val wheelSize: Double
}
class Tricycle: Wheeled, Vehicle {
// Implement both Vehicle and Wheeled
}
Interfaces in the standard library
The Kotlin standard library uses interfaces extensively in ways that may surprise you. Understanding the roles interfaces play in Kotlin can help you write clean, decoupled “Kotliny” code.
Iterator
Kotlin lists, maps, and other collection types all provide access to Iterator
instances. Iterator
is an interface defined in the Kotlin standard library, and declares methods next()
, which should give the next element of the collection, and hasNext()
, which returns a boolean indicating whether the collection has more elements.
val cars = listOf("Lamborghini", "Ferrari", "Rolls-Royce")
val numbers = mapOf("Brady" to 12, "Manning" to 18, "Brees" to 9)
for (car in cars) {
println(car)
}
for (qb in numbers) {
println("${qb.key} wears ${qb.value}")
}
Comparable
Comparable
declares an operator function used to compare an instance to other instances.
public interface Comparable<in T> {
public operator fun compareTo(other: T): Int
}
interface SizedVehicle {
var length: Int
}
class Boat: SizedVehicle, Comparable<Boat> {
override var length: Int = 0
override fun compareTo(other: Boat): Int {
return when {
length > other.length -> 1
length == other.length -> 0
else -> -1
}
}
}
val titanic = Boat()
titanic.length = 883
val qe2 = Boat()
qe2.length = 963
println(titanic > qe2) // > false
Challenges
Create a collection of interfaces for tasks at a pet shop that has dogs, cats, fish and birds.
Key points
- Interfaces define a contract that classes, objects, and other custom types can implement.
- By implementing an interface, a type is required to conform to the interface by implementing all methods and properties of the interface.
- A type can implement any number of interfaces, which allows for a quasi-multiple inheritance not permitted through subclassing.
- The Kotlin standard library uses interfaces extensively. You can use many of them, such as
Comparable
, on your own types.
Where to go from here?
Interfaces help you decouple behavior from implementation. Since interfaces are types themselves, you can still declare an array of Vehicle
instances. The array could then contain bicycles, trucks, or cars. In addition, bicycles could be enumerations and trucks could be classes! But every Vehicle
has a particular set of properties and methods you know you must implement.