Java Notes
Learn Java Generics — type parameters, generic classes, generic methods, bounded types, wildcards, and why generics make collections type-safe at compile time.
Before Java 5, collections stored everything as Object. You could put any type into any collection — but when you took something out, you had to cast it yourself. One wrong cast meant a crash at runtime.
Generics solve this by letting you declare what type a collection holds. The compiler checks types at compile time, so mistakes are caught before the program even runs.
The Problem Without Generics
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
The compiler shows no error. The program compiles. It crashes at runtime when it tries to cast 42 to String.
With Generics — Type Safety at Compile Time
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
Line by line:
List<String>— the<String>is the type parameter. It tells Java: this list holds only Strings.names.add(42)— compile error immediately. No chance of a runtime crash.name.toUpperCase()— no cast needed. Java already knowsnameis aString.
The type parameter is like a placeholder. List<String> means "a List where E = String everywhere."
Generic Class
You can write your own generic class using a type parameter name (conventionally T):
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
Line by line:
class Box<T>—Tis a placeholder for any type.private T value;— the field's type is whateverTturns out to be.Box<String>— T becomes String.getValue()returns String.Box<Integer>— T becomes Integer.getValue()returns Integer.- Primitives (
int,double) cannot be type parameters — use wrapper classes (Integer,Double).
Generic Method
A method can have its own type parameter, independent of the class:
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
<T> before the return type declares T as this method's type parameter. Java infers the actual type from what you pass.
Bounded Type Parameters
Sometimes you want T to be only a specific kind of type — a Number, or a Comparable. Use extends to set an upper bound:
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
<T extends Number> means T can be Integer, Double, Long, Float — any subclass of Number. This lets the method call element.doubleValue() which all Numbers support.
Wildcards — ?
A wildcard ? means "any type." Used when reading from a collection when you do not care about the exact type.
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
List<? extends Number> means "a list of Number or any subclass" — List<Integer>, List<Double> etc. You can read from it but not add to it (because the exact type is unknown).
Common Mistake 1: Using Raw Types
// WRONG — raw type, no type safety
List list = new ArrayList();
list.add("hello");
list.add(42); // no error
// CORRECT
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(42); // compile error — caught immediatelyAlways specify the type parameter. Raw types exist only for backward compatibility.
Common Mistake 2: Using Primitives as Type Parameters
// WRONG — primitives not allowed
// List<int> numbers = new ArrayList<>();
// CORRECT — use wrapper classes
List<Integer> numbers = new ArrayList<>();
numbers.add(5); // autoboxing: int 5 → Integer 5 automatically
int value = numbers.get(0); // unboxing: Integer → int automaticallyCommon Mistake 3: Assuming Generic Type Is Available at Runtime
Java erases type parameters at runtime (type erasure). At runtime, List<String> and List<Integer> are both just List. This means:
List<String> strings = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
System.out.println(strings.getClass() == ints.getClass()); // true — both are ArrayListYou cannot do if (list instanceof List<String>) at runtime. The <String> is gone. This is fine for most use cases, but important to know for advanced reflection.
Practice Task
Task: Write a generic class Pair<A, B> that holds two values of potentially different types. It should have:
- A constructor
Pair(A first, B second) - Getters
getFirst()andgetSecond() - A
toString()that returns(first, second)
In main, create:
Pair<String, Integer>for a name and agePair<Integer, Boolean>for a roll number and pass/fail status
Also write a generic method swap(Pair<A, B> pair) that returns a new Pair<B, A> with values swapped.
Hint: The swap method return type is Pair<B, A>. Create new Pair<>(pair.getSecond(), pair.getFirst()).
Solution:
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
What to observe: Pair<String, Integer> and Pair<Integer, String> are treated as completely different types by the compiler. Trying to assign one to the other gives a compile error. swap() creates a genuinely different typed pair — not just swapping field values in place.
Continue the lesson path