🌐 العربية
11 Lesson 5 of 5

The Magic — Type Erasure

What really happens to generics after compilation

Bytecode T becomes Object Compiler Inserts Casts Compile-Time Only

1 A Student's Amazing Question

A smart student raises their hand and asks: "Doctor, if we have Glass<Juice>, Glass<Water>, and Glass<Honey>, when we compile, how many .class files are created? Three?"

This is an excellent question! The answer may surprise you: only ONE file. Just Glass.class. Not three. Not ten. Just one.

But how is that possible? How can one bytecode file work with Juice, Water, and Honey? Let me tell you something amazing...

2 What Is Type Erasure?

Definition
Type erasure means that generic type information exists ONLY at compile time. After compilation, all generic types are ERASED from the bytecode. The JVM never sees your <T> -- it is completely gone.

Here is the process, step by step:

  1. You write Glass<T> with type parameters in your source code.
  2. The compiler checks that all types are correct -- it acts as the spell checker.
  3. The compiler then REMOVES all type parameters from the code.
  4. T is replaced with Object (when unbounded).
  5. The compiler INSERTS the necessary casts automatically.
  6. The resulting bytecode has NO generic information whatsoever.

Visual The Erasure Process

</>
Source Code
Glass<T>
You write generic code
Compiler
Type Check + Erasure
Verifies types, then erases them
01
Bytecode
Glass.class
Object everywhere, casts inserted

3 The Glass Class — Before and After Erasure

Let's see what the compiler actually does to our beloved Glass<T> class. On the left is what you write. On the right is what the bytecode actually contains after type erasure.

Your Source Code
public class Glass<T> { private T liquid; public Glass(T liquid) { this.liquid = liquid; } public T getLiquid() { return liquid; } public void setLiquid(T liquid) { this.liquid = liquid; } }
After Type Erasure (Bytecode)
public class Glass { private Object liquid; public Glass(Object liquid) { this.liquid = liquid; } public Object getLiquid() { return liquid; } public void setLiquid(Object liquid) { this.liquid = liquid; } }
Look Carefully!
After erasure, every T has been replaced with Object. This is exactly the same as the old approach from Lesson 1! But wait -- there is a crucial difference in how the code USES this class...

4 Usage Code — Before and After Erasure

The real magic is not in the class definition -- it is in the usage. Watch what happens to the code that uses the generic class.

Your Source Code
Glass<Juice> g1 = new Glass<>(new Juice()); Juice j = g1.getLiquid(); Glass<Water> g2 = new Glass<>(new Water()); Water w = g2.getLiquid();
After Erasure (Bytecode)
Glass g1 = new Glass(new Juice()); Juice j = (Juice) g1.getLiquid(); Glass g2 = new Glass(new Water()); Water w = (Water) g2.getLiquid();
The Compiler's Secret Work
In your source code -- no casting. In the bytecode -- the compiler INSERTS the casts automatically! But here is the critical point: because the compiler already verified all types at compile time, these casts are GUARANTEED to be correct. The compiler will never insert a wrong cast. This is what makes generics safe -- the spell checker runs first, and only then does erasure happen.

5 The Brilliant Trick

Now you see the brilliant trick of generics. It is an elegant two-phase system that gives you the best of both worlds:

How Generics Really Work
  1. At compile time, the compiler acts as a spell checker -- it verifies every type is correct.
  2. After verification, it erases all type parameters (T becomes Object).
  3. It inserts the necessary casts into the bytecode.
  4. Since the compiler verified everything, these inserted casts are 100% safe.
  5. Result: you get the flexibility of Object with the safety of specific types!

This is why generics are a COMPILE-TIME feature only. At runtime, there is no trace of generics -- no <Juice>, no <Water>, no <T>. Just Object and safe casts. The JVM has no idea that generics ever existed in your source code.

6 Proof — Decompiling the Bytecode

If you do not believe me, let's prove it! If you take the compiled Glass.class file and decompile it back to Java source code (using tools like javap or any Java decompiler), you will find something very interesting...

Step 1 Compile the Glass Class

Terminal
$ javac Glass.java $ ls Glass.class Glass.java

Notice: only ONE .class file, not three! No matter how many different types you use with Glass<T> -- Glass<Juice>, Glass<Water>, Glass<Honey> -- there is always just one Glass.class.

Step 2 Decompile with javap

javap output
$ javap -c Glass.class Compiled from "Glass.java" public class Glass { public Glass(java.lang.Object); public java.lang.Object getLiquid(); public void setLiquid(java.lang.Object); }
Proof!
Every T has been replaced with java.lang.Object. The generic type information is completely gone from the bytecode! There is no mention of <T>, no mention of Juice or Water -- just plain Object. The JVM sees a perfectly ordinary, non-generic class.

7 Why Does This Matter?

Practical Implications of Type Erasure
  1. Only ONE .class file exists per generic class, no matter how many type arguments you use. Glass<Juice>, Glass<Water>, Glass<String> -- they all share the same Glass.class.
  2. Generics add ZERO runtime overhead -- no extra classes, no extra memory. The bytecode is identical to what you would have written by hand using Object.
  3. You cannot check generic types at runtime -- for example, if (g instanceof Glass<Juice>) will not work because <Juice> does not exist at runtime.
  4. This is why generics are called a "compile-time mechanism" -- they do their entire job before the program ever runs.

8 Summary — The Full Picture

Let's step back and see the complete picture of everything we have learned about generics across all five lessons.

Generics — The Complete Story
  • Generics are a tool introduced in JDK 1.5 to eliminate casting and prevent ClassCastException.
  • They work at compile time only -- the compiler verifies types, then erases them.
  • After erasure, T becomes Object and the compiler inserts safe casts automatically.
  • You can make classes, methods, and interfaces generic -- all using the same <T> mechanism.
  • Result: one method/class for all types, no casting in your code, no runtime type errors.
What's Next?
In the next lesson, we will learn about wildcard generics (?) -- a special way to make generics even more flexible. Stay tuned!

9 Key Takeaways

What You Learned in This Lesson
  1. Type erasure removes all generic type information after compilation -- generics exist only in source code, never in bytecode.
  2. T is replaced with Object in the bytecode -- every type parameter becomes Object after the compiler finishes.
  3. The compiler inserts casts automatically -- and they are guaranteed safe because the compiler verified types first.
  4. Only ONE .class file is created per generic class -- no matter how many different type arguments you use.
  5. Generics exist at compile time only -- there is no trace of them at runtime.
  6. Generics add zero runtime overhead -- the bytecode is just as efficient as manually written Object code.
  7. The compiler is the spell checker -- it verifies all types are correct before erasing them, ensuring complete type safety.
← Lesson 4: Generic Interfaces Module Hub →
Code Block Theme