Kotlin offers both declaration-site variance and use-site projections. How do each map to Java's wildcard model, and why does Kotlin prefer declaration-site?
answer
- Java = use-site only; Kotlin adds declaration-site
- decl-site = out/in on the class once
- use-site projection = wildcard at one usage
- Array is invariant -> needs projections
- decl-site -> compiler generates wildcards for Java
basics
~20 sJava has only use-site wildcards (? extends/? super at each usage). Kotlin adds declaration-site variance — out/in written once on the class — and also supports use-site projections (List<out T>) that map straight to wildcards.
solid answer
~40 sJava is **use-site only**: every place you use a generic, you decide variance with `? extends T` / `? super T`. Kotlin keeps that as **use-site projections** (`Array<out T>` = `T[]` as `? extends T`; `Array<in T>` = `? super T`), but adds **declaration-site variance**: you annotate the type parameter once (`interface List<out E>`) and every usage is automatically covariant — no wildcards needed at call sites. This eliminates the wildcard noise that plagues Java APIs (Bloch's 'wildcards are confusing'). When Kotlin code with declaration-site variance is compiled, the compiler still emits Java wildcards so Java callers see the right variance. Use-site projections map 1:1 to wildcards; declaration-site variance maps to wildcards generated at each usage in the bytecode.
code
kotlin · 8 lines// Declaration-site: variance declared once
interface Source<out T> { fun get(): T }
val s: Source<Any> = object : Source<String> { override fun get() = "x" }
// Use-site projection on an invariant class
fun copy(from: Array<out Any>, to: Array<in Any>) {
for (i in from.indices) to[i] = from[i]
}go deeper
Knows Kotlin has out/in and Java has ? extends/? super.
Distinguishes declaration-site from use-site and maps each to wildcards.
Explains why invariant classes like Array need projections and how decl-site still emits wildcards for Java.
Argues the API-design and readability tradeoffs that led Kotlin to favor declaration-site, and interop consequences.
## Two models **Use-site variance (Java's only model):** variance is specified at each *usage*. `void sink(List<? super Cat> dst)` — the wildcard appears every time you write a signature. **Declaration-site variance (Kotlin adds this):** variance is specified once, on the *type parameter declaration*. `interface Source<out T>` — now `Source<Cat>` is automatically a `Source<Animal>` everywhere, no per-use wildcard. ## Use-site projections in Kotlin Kotlin still supports use-site variance, called **projections**, for invariant classes like `Array`: ```kotlin fun fill(dst: Array<in String>, value: String) { dst[0] = value } // ? super String fun copyOut(src: Array<out Any>) { val x: Any = src[0] } // ? extends Any ``` These map directly to Java wildcards: `Array<out T>` -> `T[]` viewed as `? extends T`, `Array<in T>` -> `? super T`. ## Why Kotlin prefers declaration-site - **Less noise**: declare variance once instead of repeating `? extends`/`? super` at every call. Java's standard library is littered with `Collection<? extends E>`. - **Clearer intent**: the class author states the contract (`out E` = read-only producer) rather than each caller re-deriving it. - **Fewer mistakes**: callers can't forget a wildcard and accidentally over-constrain an API. ## How both compile to Java The JVM only has wildcards, so: - A use-site projection compiles to the obvious wildcard. - Declaration-site `out`/`in` causes the compiler to *generate* wildcards in each method signature where the variant parameter is used, so Java consumers still get PECS-correct types (subject to the omission heuristics for final classes etc.). ## Quick mapping table | Kotlin | Java | |--------|------| | `interface List<out E>` (decl-site) | wildcards generated per use | | `Array<out T>` (use-site) | `T[]` as `? extends T` | | `Array<in T>` (use-site) | `? super T` | | `Foo<*>` | `Foo<?>` | ## Mental model Declaration-site = "the class is variant, forever." Use-site = "this one usage is variant." Java only ever had the second; Kotlin gives you both and nudges you toward the first.
- Why does `Array<T>` need use-site projections instead of declaration-site variance?`Array` is invariant — it both reads and writes `T` — so it can't be declared `out` or `in`; callers project per use.
- Does declaration-site variance remove wildcards from the compiled bytecode?No — wildcards still appear in generated Java signatures; declaration-site only removes them from Kotlin *source* call sites.
Declaration-site variance is a company-wide dress code (set once); use-site is telling each employee what to wear every morning.
saying these in an interview costs you the question
- Claiming Kotlin has no use-site variance
- Saying Java supports declaration-site variance
- Thinking declaration-site variance avoids wildcards in bytecode entirely
- Trying to declare `Array<out T>` at the class level (Array is invariant)