Java Generics allow developers to write type-safe, reusable, and flexible code, especially when dealing with the Collections Framework. Without generics, we would be stuck with type casting and runtime errors. With generics, we get compile-time type checking, better readability, and improved developer productivity.
In this tutorial, we'll break down how generics are used in Lists, Sets, Maps, and other collections—plus explain wildcard types, bounded types, and functional programming support.
📚 What Are Generics in Java?
Generics enable classes, interfaces, and methods to operate on typed parameters without specifying the actual type at the time of implementation.
Before Generics
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // Type cast required
With Generics
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // No cast needed
🧩 Why Generics Matter in Collections
- Type safety: Catch errors at compile time
- Eliminate casting: Cleaner, safer code
- Support polymorphism: Through wildcards (
?
), bounded types - Enable code reuse: Generic methods work across multiple types
🔧 Generic Syntax in Collections
List
List<Integer> numbers = new ArrayList<>();
Set
Set<String> names = new HashSet<>();
Map
Map<String, Integer> ageMap = new HashMap<>();
🎯 Wildcards and Bounds
Unbounded Wildcard
List<?> list = new ArrayList<String>();
Used when the type is unknown but you don’t plan to modify it.
Upper Bounded Wildcard (extends
)
List<? extends Number> list = new ArrayList<Integer>();
Used for reading values (covariant).
Lower Bounded Wildcard (super
)
List<? super Integer> list = new ArrayList<Number>();
Used for writing values (contravariant).
💡 Generic Methods in Action
public <T> void printList(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
⚙️ Internals and Compilation
- Generics use type erasure: types are erased at compile-time and replaced with Object or bounds.
- No generic arrays allowed:
new List<String>[]
is illegal. - Cannot use primitive types directly (use wrapper types like
Integer
,Double
)
🚀 Performance Considerations
Feature | Without Generics | With Generics |
---|---|---|
Compile-time checks | ❌ | ✅ |
Type casting | Required | Avoided |
Runtime errors | Possible | Minimized |
🌍 Real-World Use Cases
Map<String, List<User>>
: Grouping users by categoryList<? extends Shape>
: Rendering shapes from multiple subtypesSet<Predicate<String>>
: Managing filters dynamically
✅ Best Practices
- Always prefer generic types for collections
- Use wildcards in method parameters if needed (
List<?>
,List<? extends T>
) - Avoid raw types (
List list
) - Use bounded generics to express constraints
- Don’t mix raw and parameterized types
🔄 Java 8+ Functional Style with Generics
Map<String, List<String>> data = new HashMap<>();
data.values().stream()
.flatMap(List::stream)
.filter(s -> s.length() > 2)
.forEach(System.out::println);
📌 What's New in Java?
Java 8
- Lambdas + Streams with generics
- Functional interfaces:
Predicate<T>
,Function<T, R>
Java 9
- Factory methods with generics:
List.of("a", "b")
- Diamond operator
<>()
improvements
Java 10+
var
works with generics for local inference
Java 21
- Pattern matching and structured concurrency: works well with generic collections for data processing
📘 Conclusion and Key Takeaways
- Generics make collections safer, more powerful, and reusable.
- Understand wildcard types (
?
,? extends
,? super
) to write flexible APIs. - Use functional programming idioms with generics for expressive and concise code.
- Avoid raw types and embrace parameterization.
❓ FAQ – Java Generics in Collections
-
Can I use primitive types in generics?
No, use wrapper classes likeInteger
,Double
. -
What is type erasure?
Java removes generic type information during compilation. -
What’s the difference between
<?>
and<T>
?<?>
is an unknown wildcard;<T>
is a declared generic type. -
What happens if I mix raw and generic types?
It compiles with a warning and can lead to runtime issues. -
Can I create an array of generics?
No, generics and arrays don’t mix due to type erasure. -
When to use
? extends
vs? super
?
Use? extends
to read,? super
to write. -
Can I overload a method by changing the generic type?
No, type erasure makes that illegal. -
What is a generic method?
A method that works on generic types, declared as<T> T method(...)
. -
Are wildcards needed in return types?
Usually not. Use them in method arguments for flexibility. -
What’s a common anti-pattern with generics?
Using raw types likeList
instead ofList<T>
.