1 Remember the Problem
In Lesson 1, we saw that using Object for generic programming requires casting, and casting can cause ClassCastException at runtime. The compiler could not help us — it saw nothing wrong. The error only appeared when the program was running, and by then it was too late.
Now let us solve this problem using generics. We want the compiler to be our spell checker — catching type mistakes before we run the program.
2 Defining a Generic Class
The idea is simple. After the class name, we add angle brackets with a type parameter inside:
T is a placeholder — it stands for any type. It could be Juice, Water, Honey, or anything else. We do not decide what T is when we write the class. We decide when we create an object of the class.
Think of it like a blank line on a form. The form says “Name: ______”. The blank is a placeholder. You fill it in later when you actually use the form.
Convention Naming Type Parameters
By convention, Java uses single uppercase letters for type parameters. You could use any name (like ABC or MyType), but the convention is single letters. This makes it immediately clear that you are looking at a type parameter, not a regular class name.
T — Type (the most common, general-purpose parameter)
E — Element (used by Java collections, e.g., ArrayList<E>)
K — Key (used in maps, e.g., Map<K, V>)
V — Value (used in maps, e.g., Map<K, V>)
N — Number
S, U — Second and third type parameters when T is already taken
3 The Glass Class — Generic Version
Let us now write the full Glass class using generics. Compare this mentally with the Object-based version from Lesson 1 — notice how every Object has been replaced with T.
Notice: everywhere we used Object before, we now use T. The field type is T. The getter returns T. The setter accepts T. The type is no longer fixed — it will be decided when we create an object of this class.
4 Using the Generic Class
Now comes the beautiful part. When we create a Glass object, we specify what type T should be. We put the actual type inside angle brackets. Watch — no casting anywhere!
The compiler knows exactly what type is in each glass. juiceGlass holds Juice, waterGlass holds Water, honeyGlass holds Honey. When you call getLiquid(), it returns the exact type you specified — no casting, no guessing, no risk.
5 Compile-Time Safety — Side-by-Side Comparison
Let us put the old approach and the new approach side by side so you can see exactly what changed.
Safety What If You Try the Wrong Type?
Here is where generics truly shine. What happens if a developer accidentally tries to put Water into a glass that was declared for Juice?
The error is caught at compile time, not runtime! The spell checker is working. You cannot even run the program until you fix this mistake. This is exactly what we wanted.
With the old Object-based Glass, the same mistake would compile just fine. The compiler would see nothing wrong. Then at runtime — CRASH! A ClassCastException appears out of nowhere. With generics, this entire category of bugs is eliminated.
6 One Class File — A Student’s Question
A smart student raises their hand: “Doctor, we created Glass<Juice>, Glass<Water>, and Glass<Honey>. When we compile, do we get three separate .class files?”
Excellent question! No — you get only ONE file: Glass.class. Not three. Just one.
This might seem surprising. If there is only one class file, how does Java keep the types separate? That is a great follow-up question, and we will answer it in Lesson 5 when we study Type Erasure.
No matter how many different type parameters you use — Glass<Juice>, Glass<Water>, Glass<String>, Glass<Integer> — the compiler produces only a single Glass.class file. The type checking happens at compile time, then the type information is removed. We will learn exactly how this works in Lesson 5 (Type Erasure).
7 Key Takeaways
- Add <T> after the class name to make it generic.
- Use T wherever you would have used Object.
- When creating an object, specify the actual type: new Glass<Juice>()
- No casting needed — the compiler enforces type safety.
- Wrong types are caught at compile time, not runtime.
- Only ONE .class file is produced regardless of type parameters.
- Convention: T (Type), E (Element), K (Key), V (Value).
We made a generic class. But what if you do not need an entire class — what if you just need a single method that works with any type? In the next lesson, we will learn about Generic Methods.