Erased vs reified generics: what are the trade-offs of Java's erasure design versus reified generics like C#, and how do projects like Valhalla relate?
answer
- Erased = compile-time only; reified = runtime type arg kept
- Java erasure driven by migration/backward compatibility
- Reified enables new T[], typeof(T), no-boxing value types
- Erasure cost: boxing of List<Integer>, no runtime type ops
- Valhalla = value types + primitive specialization, not full reification
basics
~20 sErased generics (Java) drop type arguments at runtime, giving backward compatibility and one class per generic type but losing runtime type info. Reified generics (C#) keep the arguments at runtime, enabling new T[] and typeof(T), at the cost of more runtime machinery and a clean break from old code.
solid answer
~50 sJava chose **erasure**: type arguments are compile-time only, so generic and pre-generics code interoperate (migration compatibility), there is one class file per generic type regardless of instantiation, and the JVM needed no new bytecode. The price is no runtime type info — no `new T[]`, `T.class`, `instanceof List<String>`, no per-argument specialization, and reflection only recovers types from *declarations* via the Signature attribute. **C#/.NET reified** generics keep the argument at runtime: `typeof(List<int>)` is distinct from `typeof(List<string>)`, `new T[]` and runtime type tests work, and value-type instantiations like `List<int>` are *specialized* to avoid boxing — at the cost of a CLR-level mechanism, more metadata, and the fact that .NET could break from a pre-generics world Java couldn't. Java's **Project Valhalla** does not reify generics wholesale; it adds value/primitive classes and *generic specialization over primitives* so future generics can avoid boxing, narrowing erasure's performance gap while preserving compatibility.
go deeper
Knows Java generics are erased (gone at runtime) and that some other languages keep them, without the trade-off detail.
Contrasts erased vs reified at a high level: Java can't do new T[]/T.class; C# can; Java keeps backward compatibility.
Articulates the concrete trade-offs (compatibility, code size, boxing, runtime type ops) and the workarounds erasure forces; aware Valhalla targets boxing.
Frames erasure as a constraint-driven 2004 decision, separates expressiveness vs performance costs, accurately positions Valhalla (specialization, not full reification), and avoids absolutist judgments.
## Two design philosophies **Generics** let a type be parameterized by another type (`List<T>`). The deep design question is: *does the type argument exist at runtime?* - **Erased (Java):** No. The argument is a compile-time fiction used for checking, then stripped (see the erasure topic). Runtime sees only the raw type. - **Reified (C#/.NET):** Yes. `List<int>` and `List<string>` are genuinely different runtime types the CLR knows about. ## Why Java picked erasure: migration compatibility Generics arrived in Java 5 (2004), ~9 years into a huge ecosystem of compiled, pre-generics code. The overriding constraint was that **old code and new generic code must interoperate seamlessly**: - A generified `ArrayList` must still be usable by code written against the raw `ArrayList`, and vice versa (this is why **raw types** exist and why `List` and `List<String>` are assignment-compatible with only warnings). - A single, unchanged-on-the-surface class file serves every instantiation — no class-size explosion, no new bytecode, no JVM changes. Erasure delivered all of this. .NET, designed with generics from a clean slate (generics shipped in .NET 2.0, 2005, but the platform was young), did not carry that constraint and could afford a runtime mechanism. ## What each design buys and costs | Aspect | Java (erased) | C# (reified) | |---|---|---| | Runtime type arg | Gone (recoverable only from declarations) | Present on the type | | `new T[]`, `T.class`/`typeof(T)`, `instanceof T` | Forbidden | Allowed | | One class per generic type | Yes (compact) | Specialized per value-type arg | | Value types (`List<int>`) | Boxed to `List<Integer>` (heap, indirection) | Specialized, no boxing | | Backward/migration compat | Excellent | N/A (clean start) | | Runtime machinery / metadata | Minimal | Heavier (JIT specialization, type metadata) | | Reflection on generics | Via Signature attribute (declarations only) | Full runtime generic reflection | ## The boxing cost is the sharpest edge Under erasure, `List<Integer>` actually stores boxed `Integer` objects — each `int` becomes a heap object with a header, hurting memory density and cache behavior. C#'s `List<int>` stores raw `int`s contiguously. For numeric/data-heavy workloads this is Java's most painful erasure cost, and the main thing Valhalla targets. ## Project Valhalla — the nuanced answer A common misconception is that Valhalla 'reifies Java generics'. It largely does **not**. Its pillars: 1. **Value classes / primitive classes** — identity-free types the JVM can flatten and stack-allocate (no header, no indirection). 2. **Generic specialization over primitives** — letting future generics be instantiated over `int` directly, so `List<int>` need not box. This narrows erasure's *performance* gap. Valhalla deliberately keeps the *compatibility* benefits of erasure (no per-reference-type class explosion, existing code keeps working) while attacking the boxing cost. It is not a switch to C#-style full reification of reference-type arguments. ## How to discuss this as a principal - Frame it as a *constraint-driven* decision: erasure was the right call **given Java's compatibility mandate in 2004**, not a mistake. - Distinguish the two costs of erasure: (a) **expressiveness** (no runtime type ops) — mitigated idiomatically by Class/Type tokens and the Signature attribute; (b) **performance** (boxing) — addressed structurally by Valhalla. - Note the surprising upside: erasure's 'one class file' keeps code size and class-loading lean, and reflection still recovers declared generic types, so most framework needs are met without reification. - Avoid absolutist claims ('erasure is bad'): each model is internally coherent for its platform's history.
- Concretely, what does erasure cost at runtime for List<Integer> that C#'s List<int> avoids?Boxing: each int is wrapped in a heap-allocated Integer (object header + pointer indirection), hurting memory footprint and cache locality. C# specializes List<int> to store raw ints contiguously. This is the gap Valhalla's value classes and primitive generic specialization target.
- Does Project Valhalla make Java generics fully reified?No. It adds value/primitive (identity-free, flattenable) classes and specialization of generics over primitives to remove boxing, while keeping erasure's compatibility and 'one class file per generic type' benefits. It is a performance fix, not a switch to C#-style runtime reification of reference-type arguments.
saying these in an interview costs you the question
- Saying Valhalla reifies Java generics like C#
- Calling erasure a 'mistake' without the 2004 compatibility context
- Claiming reified generics have no runtime cost
- Forgetting that boxing is erasure's main performance penalty
- Asserting reflection can't see any generic info under erasure (it sees declarations)