Why Generics Are Needed
1. Java collections like List, Set, Map are based on generics.
2. Understanding generics is important to understand collections properly.
Demo 0 – Life Without Generics
Book class: Simple POJO with id (int) and name (String).
BookRecord class:
o Stores books in a fixed-size array (size = 10).
o Methods:
addItem(Book book) → Adds a book to the first null position.
getItem(int index) → Returns the book at the given index if valid;
else null.
Laptop class: Similar to Book class but represents laptops.
LaptopRecord class: Same logic as BookRecord but for laptops.
Problem identified:
Code repetition: BookRecord and LaptopRecord are almost identical, except for type.
Demo 1 – Using Object Instead of Specific Type
Created ObjectRecord:
o Stores Object[] items instead of specific type arrays.
o Methods: addItem(Object item), getItem(int index) → Returns
Object.
Usage:
o Can store Books and Laptops in the same record.
o Issue: When retrieving, you need explicit type casting:
o Book book = (Book) [Link](0);
o Laptop laptop = (Laptop) [Link](0);
Advantages:
o Single class can store any type of object.
Disadvantages:
o Type casting is required, which can lead to runtime errors.
Generic Class in Java
Definition: A generic class is a class defined with one or more type parameters,
allowing it to work with any data type.
Purpose: Reuse the same class for multiple data types while maintaining type safety.
Syntax of Generic Class
class Record<E> {
private E[] items;
public void addItem(E item) { /*...*/ }
public E getItem(int index) { /*...*/ }
}
E is a type parameter (can be any letter/word, conventionally single uppercase
letter).
E is not a class; it just represents the element type.
Benefits over Object:
1. Type safety – prevents adding wrong types.
2. No type casting required when retrieving items.
Key Points
Can replace E with any letter or word, but single letters are preferred.
Cannot create an array with new E[] directly → need type casting from Object[].
Example of creating a specific generic instance:
Record<Book> bookRecord = new Record<>();
Record<Laptop> laptopRecord = new Record<>();
Now bookRecord can only accept Book, laptopRecord can only accept Laptop.
Using Object with Generics
If you create Record<Object>:
o Can store any object (Book, Laptop, String).
o Returns Object, so type casting may be needed when retrieving.
Bad practice: Using generics with Object loses the type safety benefit.
Important Rules
1. Generics do not support inheritance:
o Record<Laptop> is not compatible with Record<Object> or Record<Book>.
2. From Java 7 onwards, you can use diamond operator (<>) on the right-hand side:
Record<Laptop> laptopRecord = new Record<>();
Wrapper Classes
Why Wrapper Classes?
Generics in Java cannot use primitive types (like int, float) directly.
Wrapper classes are object representations of primitives, allowing their use in
generics.
Primitive → Wrapper mapping:
Primitive Wrapper Class
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
Advantages: Objects can have methods, can be used in generics, and allow type
conversion.
Example:
Record<Integer> record = new Record<>(10); // Not int, use Integer
Record<Float> recordFloat = new Record<>(5.5f); // Not float, use Float
Generic Methods vs Generic Classes
Generic Class
A class defined with a type parameter <E>.
Can have:
o Generic methods (E add(E item))
o Non-generic methods
Example:
class Box<E> {
private E item;
void addItem(E item) { [Link] = item; } // Non-generic method
E getItem() { return item; } // Generic method using
class type
}
Generic Method
A method with its own type parameter. Can exist in generic or non-generic classes.
Type parameter <T> is declared before the return type.
Example:
class Utils {
public static <T> T findItem(T[] array, T item) {
for (T element : array) {
if ([Link](item)) return element;
}
return null;
}
}
Rules:
Declare type parameter before return type: public static <T> T method(...)
Can use multiple type parameters: <T, U, V>
Practical Example: Find Object
Problem
Want a find method for Book and Laptop.
Initially created two separate methods: findBook() and findLaptop().
Duplicated logic → maintenance nightmare.
Attempt 1: Using Object
public static Object findObject(Object[] arr, Object obj) { ... }
Works, but requires type casting:
Book foundBook = (Book) findObject(books, new Book(1));
Laptop foundLaptop = (Laptop) findObject(laptops, new Laptop(2));
Risk: passing wrong type (book array with laptop) → returns null.
Solution: Generic Method
public static <E> E find(E[] array, E item) {
for (E element : array) {
if ([Link](item)) return element;
}
return null;
}
Benefits:
Single method works for any type
No type casting required
Compile-time type safety (cannot pass a Laptop array to find a Book)
Important:
Always override equals() (and hashCode()) for custom comparison (e.g., compare
by id).
Notes on Multiple Type Parameters
If a method needs to handle multiple types:
public static <T, U> void process(T t, U u) { ... }
Not mandatory in simple cases; <E> is sufficient for find() example.
Wildcards in Java Generics
Problem
When you try to pass a List<Integer> to a method that expects List<Number>, it fails.
Why?
Because generics don’t allow inheritance:
List<Integer> intList = [Link](1, 2, 3);
List<Number> numList = intList; // ❌ Not allowed
Even though Integer is a subclass of Number, List<Integer> is not a subclass of
List<Number>.
Simple method for a specific type
private static void printIntegers(List<Integer> list) {
[Link](list);
}
Works only for List<Integer>
Won’t work for List<Number> or List<Double>
Parent (superclass) flexibility using ? super T
If you want to accept a type and its parent(s), use:
private static void printIntegerAndParents(List<? super Integer> list) {
[Link](list);
}
Accepts: List<Integer>, List<Number>, List<Object>
Does not accept: List<Double> or List<String>
Think of ? super T as “T or any superclass of T”.
Child (subclass) flexibility using ? extends T
If you want to accept a type and its child(ren), use:
private static void printNumberAndChildren(List<? extends Number> list) {
[Link](list);
}
Accepts: List<Integer>, List<Double>, List<Float>
Does not accept: List<Object> or List<String>
Think of ? extends T as “T or any subclass of T”.
Anything (unbounded wildcard)
If you want to accept any type, use:
private static void printEverything(List<?> list) {
[Link](list);
}
Accepts: List<Integer>, List<Double>, List<String>, etc.
Most flexible, no type restriction.
Key Notes on Wildcards
Wildcard Type Meaning Example Accepts
? Any type Anything (Integer, String, etc.)
? extends T T or any subclass of T Integer & Double if T=Number
? super T T or any superclass of T Integer, Number, Object if T=Integer
Wildcards allow limited inheritance flexibility in generics
Makes code reusable for multiple related types without duplicating methods
Example Summary
List<Integer> ints = [Link](1, 2, 3);
List<Double> doubles = [Link](1.1, 2.2);
List<Number> numbers = [Link](10, 20.5);
printIntegerAndParents(ints); // ✅ Integer or parent
printIntegerAndParents(numbers); // ✅ Parent allowed
// printIntegerAndParents(doubles); // ❌ Not allowed
printNumberAndChildren(numbers); // ✅ Number or child
printNumberAndChildren(ints); // ✅ Child allowed
// printNumberAndChildren(doubles); // ✅ Child allowed
// printNumberAndChildren(strings); // ❌ Not allowed
printEverything(ints); // ✅ Any type
printEverything(doubles); // ✅ Any type
printEverything(numbers); // ✅ Any type
Tip
Use ? super T → when writing to a collection (add elements)
Use ? extends T → when reading from a collection (get elements)
This is sometimes called PECS rule:
Producer Extends, Consumer Super