When referencing a type parameter T across a generic class's members, what is the default upper bound of an unbounded T, and how do star projections (Box<*>) relate to instances of a generic class?
answer
- Unbounded T defaults to Any? (nullable)
- T : Any removes nullability
- Box<*> = some unknown type argument
- Read through <*> as upper bound; can't write arbitrary T
- Star projection = type-safe Java raw type
basics
~20 sAn unbounded T defaults to the nullable top type Any?, so T can be null unless you constrain it. Box<*> is a star projection — a Box of some unknown type you can read from safely but can't pass specific values into.
solid answer
~40 sIf you declare `class Box<T>` with no constraint, `T`'s implicit upper bound is `Any?` — the nullable root of the type hierarchy — so any type, including nullable ones, can be a `T`, and a `T`-typed property may legitimately hold `null`. A **star projection** `Box<*>` means "a `Box` of some specific but unknown type argument." When you reference `T` through a `Box<*>`: anything the class *produces* as `T` is seen as its upper bound (`Any?`), and anything that *consumes* `T` (a parameter of type `T`) becomes uncallable with a concrete value except `null`-compatible cases — the compiler forbids passing an arbitrary value because the real type is unknown. Star projection is Kotlin's safe stand-in for Java's raw types, preserving type safety while letting you handle a heterogeneous collection of `Box<*>`.
code
kotlin · 15 linesclass Box<T>(var value: T)
fun total(boxes: List<Box<*>>) {
for (b in boxes) {
val v: Any? = b.value // read OK, typed as Any?
// b.value = 5 // ERROR: can't write through Box<*>
println(v)
}
}
class NonNullBox<T : Any>(val value: T) // T bounded: no nulls
fun main() {
total(listOf(Box(1), Box("hi"), Box(null)))
}go deeper
Knows T can be null unless bounded, and recognizes Box<*> as 'a box of something'.
States the Any? default explicitly and uses T : Any to require non-null.
Explains read-vs-write asymmetry through a star projection and why writes are blocked.
Connects star projection to Java raw-type migration and reasons about API boundaries that expose Box<*> vs concrete generics.
## The default upper bound of T When you write a generic class with a bare parameter: ```kotlin class Box<T>(val value: T) ``` `T` has an **implicit upper bound** of `Any?` — the **nullable top type** that every Kotlin type is a subtype of. Consequences: - `T` may be substituted by **any** type, including **nullable** types like `String?`. - A property of type `T` can hold `null`: `Box<String?>(null)` is valid, and inside the class `value` may be null. - If you want to forbid nulls, give an explicit non-null bound: `class Box<T : Any>(val value: T)` — now `T` is `Any` (non-null) and `Box<String?>` won't compile. This matters when referencing `T` across members: a return type `T` could be nullable, so calling `.length` on a returned `T` without a smart-cast or `?.` is unsafe unless `T` is bounded by a non-null type. ## Star projection: Box<*> Sometimes you hold a generic instance but don't know or care about its exact type argument: ```kotlin val boxes: List<Box<*>> = listOf(Box(1), Box("hi")) ``` `Box<*>` is a **star projection**: "a `Box` of *some* specific type, but I don't know which." It is **not** the same as `Box<Any?>` (which says the argument *is* `Any?`). ### Reading vs. writing through Box<*> Given `class Box<T>(var value: T)`: - **Producing T** (reading `value`) is allowed; the result type is the **upper bound**, here `Any?`. You can read but only as `Any?`. - **Consuming T** (calling a method that takes a `T` parameter, or assigning to `var value`) is **forbidden** with a concrete value, because the real type argument is unknown and the compiler can't prove safety. The exception is values the bound guarantees (e.g. `null` for a nullable bound). This is the projection analogue of variance: `<*>` behaves like `out upperBound` for produced positions and `in Nothing` for consumed positions. ## Why star projection exists It is Kotlin's **type-safe replacement for Java raw types** (`Box` with no argument). Raw types throw away type checking; `Box<*>` keeps it. Use it when you genuinely don't need to know the argument — logging, counting, structural inspection. ```kotlin fun describe(b: Box<*>): String = "Box holding ${b.value}" // value seen as Any? ``` ## Summary - Unbounded `T` → upper bound `Any?` → nullable allowed. - `class Box<T : Any>` removes nullability. - `Box<*>` = unknown argument; read as upper bound, can't write arbitrary values. - `Box<*>` ≠ `Box<Any?>`; the former hides the real type, the latter fixes it.
- How do you stop a type parameter from accepting nullable types?Give it an explicit non-null upper bound: `class Box<T : Any>`. Then `Box<String?>` won't compile.
- Is Box<*> the same as Box<Any?>?No. Box<Any?> says the argument IS Any?; Box<*> says there is some specific unknown argument, so writing arbitrary values is forbidden even though reads come back as Any?.
Box<*> is a wrapped gift: you can see there's something inside (read as Any?) but you can't safely put a specific new item in without knowing what type the box is meant for.
saying these in an interview costs you the question
- Claiming unbounded T defaults to Any (non-null) rather than Any?
- Treating Box<*> as identical to Box<Any?>
- Saying you can freely assign concrete values through a Box<*>
- Forgetting a returned T may be nullable when T is unbounded