liveBook · Manning

Contravariance

As explained in Kotlin in Action, 2E

A contravariant type parameter is a generic type parameter that reverses the subtype direction of its type arguments and is restricted to in positions.

Contravariance lets a consumer of broad values stand in for a consumer of narrower values. In Kotlin, mark a type parameter with `in` when it is used only as input: if `Cat` is a subtype of `Animal`, then `Consumer<Animal>` is a subtype of `Consumer<Cat>`. The generic subtype direction is reversed because a consumer able to accept any animal can safely accept a cat. section 11.3.4

interface Comparator<in T>  {
    fun compare(e1: T, e2: T): Int { /* ... */ }    #1
}

`Comparator` is contravariant because it receives values of `T` through `compare` rather than producing them. section 11.3.4

Reusing a general comparator

A `Comparator<Fruit>` can sort both `List<Fruit>` and `List<Apple>`. It knows how to compare every `Fruit`, so it can also compare the narrower `Apple` values. Similarly, where `sortedWith` requires `Comparator<String>`, a `Comparator<Any>` is safe because it handles strings along with other values. section 11.3.4

Use contravariance to model input-oriented APIs:

  • Declare `Consumer<in T>` only when `T` occurs in input positions; a mutable structure that both accepts and returns `T` cannot use that simple declaration. section 11.3.4
  • Recognize function types as mixed variance: `(T) -> R` is contravariant in its argument and covariant in its result. A function accepting `Animal` can therefore serve where one accepting `Cat` is needed. section 11.3.4 section 11.4
  • Treat `FieldValidator<in T>` as a consumer: its `validate(input: T)` method makes a validator for a broader input type usable for a narrower one. section 11.3.6

Star projection deliberately removes the type argument. For a contravariant `Consumer<in T>`, `Consumer<*>` behaves as `Consumer<in Nothing>`: because its accepted input type is unknown, methods that take `T` cannot be called. This protects a heterogeneous validator map, but retrieving a validator with an unchecked cast can still select the wrong validator and fail at runtime. Encapsulate registration and typed retrieval to keep that cast in one controlled place. section 11.3.6 section 11.3.6

Contravariance is the input-side counterpart to covariance. Covariant `Producer<out T>` preserves subtype direction, contravariant `Consumer<in T>` reverses it, and invariant types provide no subtype relationship across different type arguments. section 11.3.4 section 11.4

How Kotlin in Action, 2E covers it

How deep it goes
Defines reversed generic subtyping through Comparator and function examples, then applies it to star-projected validators and casts.
Examples
Uses fruit weight comparators, an Animal extension passed for cats, and a `KClass`-keyed `FieldValidator` registry.
What it assumes
Assumes familiarity with Kotlin generics, subtypes, and generic interfaces while defining contravariant subtype reversal directly.
What you can do after
Evaluate/understand when an `in`-typed consumer can safely replace a consumer of a narrower type and why star projections reject inputs.
How it connects
Connects contravariance to covariance, invariance, declaration-site variance, function types, star projections, and type erasure.

Also explained in

Compare how 2 books explain contravariance →

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.