Chapters

Hide chapters

Kotlin Apprentice

Second Edition · Android 10 · Kotlin 1.3 · IDEA

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Building Your Own Types

Section 3: 8 chapters
Show chapters Hide chapters

Section IV: Intermediate Topics

Section 4: 9 chapters
Show chapters Hide chapters

17. Interfaces
Written by Joe Howard

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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.

Introducing interfaces

You define an interface much as you do any other custom type:

interface Vehicle {
  fun accelerate()
  fun 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 in Java 8, you can define default implementations for the methods in an interface:

interface SpaceVehicle {
  fun accelerate()
  fun stop() {
    println("Whoa, slow down!")
  }
}

class LightFreighter: SpaceVehicle {
  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:

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

  1. Create an interface Area that defines a read-only property area of type Double.
  2. Implement Area with classes representing Square, Triangle, and Circle.
  3. 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

Pet shop tasks

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.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now