The Liskov Substitution Principle states that:

Objects in a program should be replaceable with instances of their sub-types without altering the correctness of that program.

Assuming that:

interface Iterable<T> {
    fun getIterator(): Iterator<T>
}

interface Collection<T> : Iterable<T> {
    val size: Int
}

interface List<T> : Collection<T> {
    fun get(index: Int): T
}

interface MutableList<T> : List<T> {
    fun set(index: Int, item: T): Unit
}

When LSP is applied to input parameters, the lowest-level abstraction should be applied:

DO

fun foo(items: Iterable<Any>) { ... }

DON'T

fun foo(items: List<Any>) { ... }

But, does LSP apply to function return types, and if so, does the reverse apply?

fun bar(): Iterable<Any> { ... }

OR

fun bar(): List<Any> { ... }

1 Answers

1
ComDubh On Best Solutions

Yes and yes. In order to comply with the LSP, argument types in an overriding method must be contravariant, as you point out. The reverse is true for the return type -- this must be covariant, i.e. of the same type, or a more specific type, as the return type in the method being overidden.

Think of the slogan "demand no more, promise no less." Let's say the superclass method returns a Rectangle. This method can be overidden to return a Square, as this "promises more," but not to return a Shape, as this would "promise less."