liveBook · Manning

Subtyping

As explained in Kotlin in Action, 2E

A subtyping relation specifies when one type can be used wherever another type is expected.

Subtyping answers a practical Kotlin question: may this value go where that type is expected? Type B is a subtype of type A when a B value is valid wherever an A value is required. Every type is a subtype of itself; the reverse relationship is a supertype. The compiler applies this rule to assignments and function arguments. section 11.3.2

Use a more specific value where a general one is expected

```kotlin fun test(i: Int) { val n: Number = i // Int is a subtype of Number fun f(s: String) { } f(i) // does not compile: Int is not a subtype of String } ``` In straightforward class hierarchies, subtyping often follows subclassing: `Int` is a subtype of `Number`, and a class implementing an interface is a subtype of that interface, such as `String` and `CharSequence`. The important test is always usability at the required type, not merely a shared class name. section 11.3.2

Figure 11.5 B is a subtype of A if you can use it when A is expected. Since you can use an Int where a Number is expected, it is a subtype. Likewise, you can use an Int where an Int is expected; it is also a subtype of itself. Because you can’t use an Int where a String is expected, it can’t be considered a subtype.
CH11_F05_Isakova

Use the expected-type test visually: `Int` can stand in for `Number` and for itself, but not for `String`. section 11.3.2

A class name does not identify just one Kotlin type. `String` and nullable `String?` are distinct types, and generic classes produce types such as `List<Int>`, `List<String?>`, and `List<List<String>>`. Nullability has its own direction: non-null `A` is a subtype of `A?`, so a `String` can be assigned to `String?`; a possibly null `String?` cannot be used where `String` is required. section 11.3.2

For generic types, matching base names is not enough. Variance determines whether a type argument’s subtype relationship carries through, reverses, or does not apply. Kotlin’s read-only `List` is covariant: `List<String>` is usable where `List<Any>` is expected. That is safe for a function that only reads values. A `MutableList<String>` cannot be passed as `MutableList<Any>`, because a function could add an `Int` and leave a list that no longer contains only strings. section 11.3.1 section 11.4

Choose the interface that preserves type safety

```kotlin fun printContents(list: List<Any>) { println(list.joinToString()) } fun addAnswer(list: MutableList<Any>) { list.add(42) } val strings = mutableListOf("abc", "bac") printContents(strings) // safe through read-only List // addAnswer(strings) // rejected: it could insert an Int ``` Read-only and mutable collection interfaces therefore express more than available operations: they control which subtype substitutions are safe. section 11.3.1

Contravariance reverses the generic relationship for types that consume values. `Comparator<in T>` receives `T` values, so a comparator able to handle `Fruit` can also compare `Apple`: `Comparator<Fruit>` is a subtype of `Comparator<Apple>`. More generally, `Consumer<A>` is a subtype of `Consumer<B>` when `B` is a subtype of `A`. A function type combines both directions: its parameter is contravariant and its return type is covariant. section 11.3.4 section 11.4

Reuse a comparator written for a supertype

```kotlin val weightComparator = Comparator<Fruit> { a, b -> a.weight - b.weight } val apples: List<Apple> = listOf(/* ... */) println(apples.sortedWith(weightComparator)) ``` The comparator accepts any `Fruit`, so every `Apple` is already a valid input. This is the reverse of a producer-like covariant type, where a more specific type argument produces a more specific generic type. section 11.3.4

Kotlin supports declaration-site variance for a generic class as a whole and use-site variance for one particular use, serving the same broad purpose as Java wildcards. Type arguments are normally compile-time information because they are erased at runtime; inline reified type parameters are the stated exception for runtime type checks. section 11.0 section 11.4

How Kotlin in Action, 2E covers it

How deep it goes
Defines ordinary and nullable subtyping, then explains covariance and contravariance through collection, comparator, and function examples.
Examples
Uses `Int` versus `Number`, nullable assignments, read-only and mutable lists, fruit comparators, and function types.
What it assumes
Builds from classes, interfaces, nullable types, and generic type arguments before introducing variance.
What you can do after
Evaluate/understand whether Kotlin assignments, calls, and generic substitutions are accepted safely by the compiler.
How it connects
Connects subtyping to nullability, generics, covariance, contravariance, declaration-site variance, use-site variance, and type erasure.

Also explained in

Compare how 4 books explain subtyping →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Kotlin in Action, 2E. The section links point to the parts of the book it draws on.