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?
Here is the process, step by step:
- You write Glass<T> with type parameters in your source code.
- The compiler checks that all types are correct -- it acts as the spell checker.
- The compiler then REMOVES all type parameters from the code.
- T is replaced with Object (when unbounded).
- The compiler INSERTS the necessary casts automatically.
- The resulting bytecode has NO generic information whatsoever.
Visual The Erasure Process
You write generic code
Verifies types, then erases them
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.
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.
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:
- At compile time, the compiler acts as a spell checker -- it verifies every type is correct.
- After verification, it erases all type parameters (T becomes Object).
- It inserts the necessary casts into the bytecode.
- Since the compiler verified everything, these inserted casts are 100% safe.
- 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
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
7 Why Does This Matter?
- 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.
- 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.
- You cannot check generic types at runtime -- for example, if (g instanceof Glass<Juice>) will not work because <Juice> does not exist at runtime.
- 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 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.
9 Key Takeaways
- Type erasure removes all generic type information after compilation -- generics exist only in source code, never in bytecode.
- T is replaced with Object in the bytecode -- every type parameter becomes Object after the compiler finishes.
- The compiler inserts casts automatically -- and they are guaranteed safe because the compiler verified types first.
- Only ONE .class file is created per generic class -- no matter how many different type arguments you use.
- Generics exist at compile time only -- there is no trace of them at runtime.
- Generics add zero runtime overhead -- the bytecode is just as efficient as manually written Object code.
- The compiler is the spell checker -- it verifies all types are correct before erasing them, ensuring complete type safety.