What are Generics?
Generics allow you to write type-safe, reusable code that works with different data types. Instead of using Object and casting, generics let the compiler enforce types at compile time.
java
// Without generics — unsafe
ArrayList list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Manual casting needed
// With generics — type-safe
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0); // No casting neededGeneric Classes
java
public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// Usage with different types
Box<String> stringBox = new Box<>("Hello");
Box<Integer> intBox = new Box<>(42);
System.out.println(stringBox.getContent()); // Hello
System.out.println(intBox.getContent()); // 42Generic Methods
java
public class Util {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
}
// Usage
Integer[] nums = {1, 2, 3};
String[] names = {"A", "B", "C"};
Util.printArray(nums); // 1 2 3
Util.printArray(names); // A B C
System.out.println(Util.max(10, 20)); // 20
System.out.println(Util.max("apple", "banana")); // bananaBounded Type Parameters
Restrict what types can be used:
java
// Upper bound — T must be Number or its subclass
public static <T extends Number> double sum(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
sum(5, 3); // ✓ Integer extends Number
sum(2.5, 3.7); // ✓ Double extends Number
// sum("a", "b"); // ✗ String does not extend NumberWildcards
java
// ? extends — read from (upper bound)
public void printList(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
// ? super — write to (lower bound)
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
// ? — unknown type
public void printAny(List<?> list) {
System.out.println("Size: " + list.size());
}Generic Interface
java
public interface Repository<T> {
void save(T entity);
T findById(int id);
List<T> findAll();
}
public class UserRepository implements Repository<User> {
private List<User> users = new ArrayList<>();
@Override
public void save(User user) { users.add(user); }
@Override
public User findById(int id) {
return users.stream().filter(u -> u.getId() == id).findFirst().orElse(null);
}
@Override
public List<User> findAll() { return users; }
}Summary
- Generics provide compile-time type safety and eliminate casting
- Use
<T>for generic classes, methods, and interfaces - Bounded types (
<T extends Number>) restrict allowed types - Wildcards (
?,? extends,? super) add flexibility to method parameters - Generics are erased at runtime (type erasure) — they exist only at compile time
- All Java Collections use generics:
ArrayList<String>,HashMap<String, Integer>