Generics in the Collections Framework
Generics in the Collections Framework
The Java Collections Framework and generics arrived together in Java 5, and that was not a coincidence — every core collection type was redesigned around type parameters at the same time. Before Java 5, List, Map, and Set stored raw Object references. Every retrieval needed a cast. Every cast was a runtime gamble. Generics changed the contract: List<E>, Map<K,V>, and Set<E> now carry the element type as part of their signature, and the compiler verifies every operation against it. This article brings together every concept from the Generics series — type parameters, bounded types, wildcards, PECS, and iterators — and shows where each one appears in the collections you write every day.
What Generics Changed for Collections
The practical difference is where errors appear. Without generics, a wrong-type insertion fails at the cast site — at runtime, possibly in production. With generics, it fails at the insertion site — at compile time, during development.
PRE-GENERICS (Java 1.4):
List orders = new ArrayList();
orders.add("ORD-001");
orders.add(42); // silently accepted - no error
String id = (String) orders.get(0); // cast - works here
String bad = (String) orders.get(1); // ClassCastException at RUNTIME
Map inventory = new HashMap();
inventory.put("P001", 120);
int qty = (int) inventory.get("P001"); // cast required - no guarantee
POST-GENERICS (Java 5+):
List<String> orders = new ArrayList<>();
orders.add("ORD-001");
// orders.add(42); <- COMPILE ERROR - caught immediately
String id = orders.get(0); // no cast - compiler knows it is String
Map<String, Integer> inventory = new HashMap<>();
inventory.put("P001", 120);
int qty = inventory.get("P001"); // Integer auto-unboxed - no cast
THE STRUCTURAL SHIFT:
Type mismatches moved from RUNTIME ClassCastException
to COMPILE-TIME error.
The cast is still in the bytecode - but the compiler verified it is correct.
Basic Overview - How Generics Appear Across the Collections API
1. TYPE PARAMETERS ON EVERY COLLECTION INTERFACE
Fresher view : every collection declares a type parameter.
List<E>, Set<E>, Queue<E>, Map<K,V>, Iterator<E>.
When you write List<String>, E = String everywhere:
add(String), get() returns String, no casts anywhere.
Deeper view : the compiler tracks E at every usage site.
List<String>.get() compiles to Object in bytecode
(type erasure) with an automatic CHECKCAST instruction
that the compiler proves will never fail.
Two parameterizations List<String> and List<Integer>
share the same ArrayList.class at runtime.
2. WILDCARDS IN COLLECTION UTILITY METHODS
Fresher view : Collections.sort(), Collections.copy(), addAll()
accept wildcards so one method works with
List<Integer>, List<Double>, List<String> - not
just one exact parameterized type.
Deeper view : Collections.copy(List<? super T> dest,
List<? extends T> src) uses full PECS.
src is the producer (? extends - read from it).
dest is the consumer (? super - write into it).
This lets dest be List<Number> when src is
List<Integer> - Integer IS-A Number.
3. BOUNDED TYPES IN SORTED COLLECTIONS
Fresher view : TreeSet and TreeMap keep elements in order.
Ordering requires comparison - so elements must
implement Comparable, or a Comparator must be
provided. The generic API enforces this choice.
Deeper view : TreeSet<E>'s no-arg constructor requires E to
implement Comparable<? super E> at runtime -
violation throws ClassCastException on first insert.
The Comparator overload TreeSet(Comparator<? super E>)
accepts any comparator for E or its supertypes,
applying the PECS consumer-super pattern.
4. Iterator<E> AND THE FOR-EACH LOOP
Fresher view : the for-each loop works because collections
implement Iterable<E>, whose iterator() method
returns Iterator<E>. That Iterator's next()
returns E - the exact element type, no cast.
Deeper view : the compiler translates for (String s : list)
into a while loop using list.iterator() and
it.next(). The CHECKCAST the compiler inserts
for the E return type is verified correct at
compile time. No cast is ever written in
application code, but the type safety guarantee
holds end to end.
List - Type-Safe Element Sequences
List<E> is the foundation of most collection usage. Its type parameter E propagates through every method: add(E), get(int) returning E, set(int, E), indexOf(Object), contains(Object), iterator() returning Iterator<E>. The two Object-based methods (contains, indexOf) remain Object for backward compatibility — they always return false or -1 rather than throwing when passed the wrong type.
1// File: ListGenericsDemo.java
2
3import java.util.*;
4import java.util.function.*;
5
6public class ListGenericsDemo {
7
8 // Generic method with bounded param - sorts any Comparable list
9 static <T extends Comparable<T>> List<T> sortedCopy(List<T> source) {
10 List<T> copy = new ArrayList<>(source);
11 Collections.sort(copy);
12 return copy;
13 }
14
15 // PECS in a custom utility - reads from producer, writes to consumer
16 static <T> void copyIf(List<? extends T> source,
17 List<? super T> dest,
18 Predicate<? super T> predicate) {
19 for (T item : source) { // source PRODUCES T (? extends)
20 if (predicate.test(item)) {
21 dest.add(item); // dest CONSUMES T (? super)
22 }
23 }
24 }
25
26 public static void main(String[] args) {
27
28 List<String> products = new ArrayList<>(
29 List.of("USB Hub", "Wireless Mouse", "Keyboard", "Laptop Stand", "Webcam"));
30
31 System.out.println("=== sortedCopy - bounded <T extends Comparable<T>> ===");
32 List<String> sorted = sortedCopy(products);
33 System.out.println("Original : " + products);
34 System.out.println("Sorted : " + sorted);
35
36 System.out.println();
37
38 System.out.println("=== sortedCopy - same method with List<Integer> ===");
39 List<Integer> prices = new ArrayList<>(List.of(1299, 499, 2499, 799, 3499));
40 List<Integer> sortedPrices = sortedCopy(prices);
41 System.out.println("Sorted prices: " + sortedPrices);
42
43 System.out.println();
44
45 System.out.println("=== copyIf - PECS: ? extends source, ? super dest ===");
46 List<Object> destObj = new ArrayList<>(); // List<Object> satisfies ? super String
47 copyIf(products, destObj, name -> name.length() > 7);
48 System.out.println("Names longer than 7 chars (into List<Object>): " + destObj);
49
50 System.out.println();
51
52 System.out.println("=== for-each uses Iterator<E> - no cast in sight ===");
53 int total = 0;
54 for (int price : prices) { // Integer unboxed to int - no explicit cast
55 total += price;
56 }
57 System.out.println("Total: Rs." + total);
58
59 System.out.println();
60
61 System.out.println("=== Collections.addAll uses ? super T internally ===");
62 List<Number> numList = new ArrayList<>();
63 Collections.addAll(numList, 10, 20, 30); // Integer added - Number satisfies ? super Integer
64 System.out.println("numList: " + numList);
65 }
66}Output:
=== sortedCopy - bounded <T extends Comparable<T>> ===
Original : [USB Hub, Wireless Mouse, Keyboard, Laptop Stand, Webcam]
Sorted : [Keyboard, Laptop Stand, USB Hub, Webcam, Wireless Mouse]
=== sortedCopy - same method with List<Integer> ===
Sorted prices: [499, 799, 1299, 2499, 3499]
=== copyIf - PECS: ? extends source, ? super dest ===
Names longer than 7 chars (into List<Object>): [Wireless Mouse, Keyboard, Laptop Stand, Webcam]
=== for-each uses Iterator<E> - no cast in sight ===
Total: Rs.8495
=== Collections.addAll uses ? super T internally ===
numList: [10, 20, 30]
Map<K,V> - Two Independent Type Parameters
Map<K,V> is the canonical two-type-parameter design. K and V are completely independent placeholders — a Map<String, Integer> uses String for keys and Integer for values, and neither constrains the other. The iteration entry type Map.Entry<K,V> inherits both parameters, giving both getKey() returning K and getValue() returning V fully typed at the call site.
1// File: MapGenericsDemo.java
2
3import java.util.*;
4import java.util.function.*;
5
6public class MapGenericsDemo {
7
8 // Inverts Map<K,V> to Map<V,K> - uses Map.Entry<K,V> to iterate
9 static <K, V> Map<V, K> invert(Map<K, V> source) {
10 Map<V, K> result = new LinkedHashMap<>();
11 for (Map.Entry<K, V> entry : source.entrySet()) {
12 K key = entry.getKey(); // K - typed, no cast
13 V value = entry.getValue(); // V - typed, no cast
14 result.put(value, key);
15 }
16 return result;
17 }
18
19 // Groups items by a key function - List<T> -> Map<K, List<T>>
20 static <T, K> Map<K, List<T>> groupBy(List<T> items, Function<T, K> keyFn) {
21 Map<K, List<T>> groups = new LinkedHashMap<>();
22 for (T item : items) {
23 groups.computeIfAbsent(keyFn.apply(item), k -> new ArrayList<>()).add(item);
24 }
25 return groups;
26 }
27
28 // Merges two maps using ? extends K and ? extends V - accepts any subtype maps
29 static <K, V> Map<K, V> merge(Map<? extends K, ? extends V> first,
30 Map<? extends K, ? extends V> second) {
31 Map<K, V> result = new LinkedHashMap<>(first);
32 result.putAll(second); // putAll accepts Map<? extends K, ? extends V>
33 return result;
34 }
35
36 public static void main(String[] args) {
37
38 Map<String, Integer> stockLevels = new LinkedHashMap<>();
39 stockLevels.put("P001-Mouse", 120);
40 stockLevels.put("P002-Stand", 45);
41 stockLevels.put("P003-USBHub", 200);
42 stockLevels.put("P004-Keyboard", 28);
43
44 System.out.println("=== Map.Entry<K,V> - both key and value fully typed ===");
45 for (Map.Entry<String, Integer> entry : stockLevels.entrySet()) {
46 String id = entry.getKey(); // String - no cast
47 Integer qty = entry.getValue(); // Integer - no cast
48 String status = qty < 50 ? "LOW" : "OK";
49 System.out.printf(" %-22s qty=%-4d [%s]%n", id, qty, status);
50 }
51
52 System.out.println();
53
54 System.out.println("=== invert - Map<String,Integer> to Map<Integer,String> ===");
55 Map<Integer, String> inverted = invert(stockLevels);
56 inverted.forEach((qty, id) -> System.out.printf(" qty=%-4d -> %s%n", qty, id));
57
58 System.out.println();
59
60 System.out.println("=== groupBy - group product IDs by stock status ===");
61 List<String> ids = List.of("P001-Mouse", "P002-Stand", "P003-USBHub", "P004-Keyboard");
62 Map<String, List<String>> byStatus = groupBy(
63 ids, id -> stockLevels.getOrDefault(id, 0) >= 50 ? "IN_STOCK" : "LOW_STOCK");
64 byStatus.forEach((status, list) ->
65 System.out.println(" " + status + ": " + list));
66
67 System.out.println();
68
69 System.out.println("=== merge two maps with ? extends K, ? extends V ===");
70 Map<String, Integer> warehouseA = Map.of("P001-Mouse", 60, "P003-USBHub", 100);
71 Map<String, Integer> warehouseB = Map.of("P002-Stand", 30, "P004-Keyboard", 15);
72 Map<String, Integer> combined = merge(warehouseA, warehouseB);
73 System.out.println(" Combined: " + combined);
74 }
75}Output:
=== Map.Entry<K,V> - both key and value fully typed ===
P001-Mouse qty=120 [OK]
P002-Stand qty=45 [LOW]
P003-USBHub qty=200 [OK]
P004-Keyboard qty=28 [LOW]
=== invert - Map<String,Integer> to Map<Integer,String> ===
qty=120 -> P001-Mouse
qty=45 -> P002-Stand
qty=200 -> P003-USBHub
qty=28 -> P004-Keyboard
=== groupBy - group product IDs by stock status ===
IN_STOCK: [P001-Mouse, P003-USBHub]
LOW_STOCK: [P002-Stand, P004-Keyboard]
=== merge two maps with ? extends K, ? extends V ===
Combined: {P001-Mouse=60, P003-USBHub=100, P002-Stand=30, P004-Keyboard=15}
Set and Sorted Collections
Set<E> uses one type parameter, but sorted sets — TreeSet<E> and SortedSet<E> — surface a concrete generic contract: either E must implement Comparable<? super E>, or a Comparator<? super E> must be supplied. The wildcard on the comparator is the PECS consumer pattern: the comparator consumes E values, so ? super E allows a Comparator<Object> to sort a Set<Product>.
1// File: SetGenericsDemo.java
2
3import java.util.*;
4
5public class SetGenericsDemo {
6
7 record Product(String id, String name, double price) {}
8
9 public static void main(String[] args) {
10
11 System.out.println("=== HashSet<String> - type-safe, no duplicates ===");
12 Set<String> categories = new HashSet<>();
13 categories.add("Electronics");
14 categories.add("Accessories");
15 categories.add("Electronics"); // silently deduplicated
16 categories.add("Furniture");
17 System.out.println("Categories : " + categories);
18 System.out.println("Has Electronics: " + categories.contains("Electronics"));
19
20 System.out.println();
21
22 System.out.println("=== TreeSet<String> - natural order via Comparable<String> ===");
23 TreeSet<String> sorted = new TreeSet<>(categories);
24 System.out.println("Sorted : " + sorted);
25 System.out.println("First : " + sorted.first());
26 System.out.println("Last : " + sorted.last());
27 System.out.println("headSet(<F) : " + sorted.headSet("F"));
28
29 System.out.println();
30
31 System.out.println("=== TreeSet with Comparator<? super Product> ===");
32 // Product does NOT implement Comparable - Comparator is required
33 // Comparator<Product> satisfies Comparator<? super Product>
34 Comparator<Product> byPrice = Comparator.comparingDouble(Product::price);
35
36 TreeSet<Product> byPriceSet = new TreeSet<>(byPrice);
37 byPriceSet.add(new Product("P001", "Wireless Mouse", 799.0));
38 byPriceSet.add(new Product("P002", "Laptop Stand", 1299.0));
39 byPriceSet.add(new Product("P003", "USB Hub", 499.0));
40 byPriceSet.add(new Product("P004", "Keyboard", 2499.0));
41
42 System.out.println("Products sorted by price:");
43 byPriceSet.forEach(p ->
44 System.out.printf(" %-20s Rs.%.2f%n", p.name(), p.price()));
45
46 System.out.println();
47
48 System.out.println("=== Set operations - addAll / retainAll use ? extends E / ? ===");
49 Set<Integer> setA = new HashSet<>(Set.of(499, 799, 1299, 2499));
50 Set<Integer> setB = new HashSet<>(Set.of(799, 1299, 3499));
51
52 Set<Integer> union = new HashSet<>(setA);
53 union.addAll(setB); // addAll accepts Collection<? extends Integer>
54 System.out.println("Union : " + new TreeSet<>(union));
55
56 Set<Integer> intersection = new HashSet<>(setA);
57 intersection.retainAll(setB); // retainAll accepts Collection<?>
58 System.out.println("Intersection: " + new TreeSet<>(intersection));
59 }
60}Output:
=== HashSet<String> - type-safe, no duplicates ===
Categories : [Accessories, Electronics, Furniture]
Has Electronics: true
=== TreeSet<String> - natural order via Comparable<String> ===
Sorted : [Accessories, Electronics, Furniture]
First : Accessories
Last : Furniture
headSet(<F) : [Accessories, Electronics]
=== TreeSet with Comparator<? super Product> ===
Products sorted by price:
USB Hub Rs.499.00
Wireless Mouse Rs.799.00
Laptop Stand Rs.1299.00
Keyboard Rs.2499.00
=== Set operations - addAll / retainAll use ? extends E / ? ===
Union : [499, 799, 1299, 2499, 3499]
Intersection: [799, 1299]
Collections Utility Methods and PECS
java.util.Collections is a non-generic class containing exclusively generic static methods. Its signatures are the most instructive PECS examples in the JDK. Reading each signature tells you exactly how the method uses its parameters.
1// File: CollectionsUtilityDemo.java
2
3import java.util.*;
4
5public class CollectionsUtilityDemo {
6
7 public static void main(String[] args) {
8
9 System.out.println("=== Collections.sort with Comparator<? super T> ===");
10 // sort(List<T> list, Comparator<? super T> c)
11 // Comparator<Object> satisfies Comparator<? super String>
12 List<String> items = new ArrayList<>(
13 List.of("Webcam", "Keyboard", "Hub", "Mouse", "Monitor"));
14
15 Collections.sort(items); // natural order
16 System.out.println("Natural : " + items);
17
18 Collections.sort(items, Comparator.comparingInt(String::length));
19 System.out.println("By length: " + items);
20
21 System.out.println();
22
23 System.out.println("=== Collections.copy - PECS in one signature ===");
24 // copy(List<? super T> dest, List<? extends T> src)
25 // src PRODUCES T values -> ? extends T (producer)
26 // dest CONSUMES T values -> ? super T (consumer)
27 List<Integer> source = List.of(499, 799, 1299, 2499);
28 List<Number> dest = new ArrayList<>(Arrays.asList(0, 0, 0, 0));
29
30 Collections.copy(dest, source);
31 // List<Number> satisfies ? super Integer (Number IS a supertype of Integer)
32 System.out.println("Copied into List<Number>: " + dest);
33
34 System.out.println();
35
36 System.out.println("=== Collections.min / max - Comparable or Comparator<? super T> ===");
37 List<Integer> scores = List.of(72, 95, 88, 64, 91, 79);
38 System.out.println("Min: " + Collections.min(scores));
39 System.out.println("Max: " + Collections.max(scores));
40
41 System.out.println();
42
43 System.out.println("=== Collections.frequency - Object parameter for backwards compat ===");
44 List<String> tags = List.of("sale", "new", "sale", "trending", "new", "sale");
45 System.out.println("'sale' count : " + Collections.frequency(tags, "sale"));
46 System.out.println("'trending' count: " + Collections.frequency(tags, "trending"));
47
48 System.out.println();
49
50 System.out.println("=== Collections.unmodifiableList - preserves type parameter ===");
51 List<String> mutable = new ArrayList<>(List.of("Alpha", "Beta", "Gamma"));
52 List<String> immutable = Collections.unmodifiableList(mutable);
53
54 String first = immutable.get(0); // String - type parameter preserved
55 System.out.println("get(0) as String: " + first.toUpperCase());
56
57 try {
58 immutable.add("Delta");
59 } catch (UnsupportedOperationException e) {
60 System.out.println("add() blocked: UnsupportedOperationException");
61 }
62 }
63}Output:
=== Collections.sort with Comparator<? super T> ===
Natural : [Hub, Keyboard, Monitor, Mouse, Webcam]
By length: [Hub, Mouse, Webcam, Monitor, Keyboard]
=== Collections.copy - PECS in one signature ===
Copied into List<Number>: [499, 799, 1299, 2499]
=== Collections.min / max - Comparable or Comparator<? super T> ===
Min: 64
Max: 95
=== Collections.frequency - Object parameter for backwards compat ===
'sale' count : 3
'trending' count: 1
=== Collections.unmodifiableList - preserves type parameter ===
get(0) as String: ALPHA
add() blocked: UnsupportedOperationException
Iterator and Iterable
The for-each loop is built on two generic interfaces. Iterable<T> declares Iterator<T> iterator(). Iterator<T> declares T next(), boolean hasNext(), and void remove(). When the compiler encounters for (String s : list), it generates a while loop using list.iterator() typed as Iterator<String> — next() returns String directly, with no explicit cast anywhere in application code.
1// File: IteratorGenericsDemo.java
2
3import java.util.*;
4import java.util.function.*;
5
6public class IteratorGenericsDemo {
7
8 // Custom generic Iterable - implements Iterable<T> for any type T
9 static class FilteredIterable<T> implements Iterable<T> {
10 private final List<T> source;
11 private final Predicate<T> filter;
12
13 FilteredIterable(List<T> source, Predicate<T> filter) {
14 this.source = List.copyOf(source);
15 this.filter = filter;
16 }
17
18 @Override
19 public Iterator<T> iterator() {
20 return new Iterator<T>() {
21 private int index = 0;
22 private T pending;
23 private boolean ready = false;
24
25 private boolean advance() {
26 while (index < source.size()) {
27 T candidate = source.get(index++);
28 if (filter.test(candidate)) {
29 pending = candidate;
30 ready = true;
31 return true;
32 }
33 }
34 ready = false;
35 return false;
36 }
37
38 @Override public boolean hasNext() { return ready || advance(); }
39
40 @Override
41 public T next() {
42 if (!ready && !advance()) throw new NoSuchElementException();
43 ready = false;
44 return pending; // returns T - fully typed
45 }
46 };
47 }
48 }
49
50 public static void main(String[] args) {
51
52 System.out.println("=== Manual Iterator<String> - next() returns String ===");
53 List<String> cities = List.of("Mumbai", "Pune", "Nashik", "Nagpur", "Aurangabad");
54 Iterator<String> it = cities.iterator();
55 while (it.hasNext()) {
56 String city = it.next(); // String - no cast
57 System.out.println(" " + city.toUpperCase());
58 }
59
60 System.out.println();
61
62 System.out.println("=== for-each compiles to the same Iterator<String> loop ===");
63 for (String city : cities) { // identical bytecode to above
64 System.out.println(" " + city.length() + " chars");
65 }
66
67 System.out.println();
68
69 System.out.println("=== Custom FilteredIterable<Integer> - only prices > 1000 ===");
70 List<Integer> allPrices = List.of(499, 1299, 799, 2499, 399, 1799, 649);
71 FilteredIterable<Integer> expensive =
72 new FilteredIterable<>(allPrices, price -> price > 1000);
73
74 System.out.print("Expensive: ");
75 for (Integer price : expensive) { // for-each uses our generic Iterator<Integer>
76 System.out.print("Rs." + price + " ");
77 }
78 System.out.println();
79
80 System.out.println();
81
82 System.out.println("=== Safe removal via Iterator.remove() ===");
83 List<Integer> mutablePrices = new ArrayList<>(
84 List.of(499, 1299, 799, 2499, 399, 1799));
85 Iterator<Integer> remover = mutablePrices.iterator();
86 while (remover.hasNext()) {
87 Integer price = remover.next(); // Integer - typed by Iterator<Integer>
88 if (price < 1000) remover.remove(); // safe in-loop removal
89 }
90 System.out.println("Remaining (>= Rs.1000): " + mutablePrices);
91 }
92}Output:
=== Manual Iterator<String> - next() returns String ===
MUMBAI
PUNE
NASHIK
NAGPUR
AURANGABAD
=== for-each compiles to the same Iterator<String> loop ===
6 chars
4 chars
6 chars
6 chars
9 chars
=== Custom FilteredIterable<Integer> - only prices > 1000 ===
Expensive: Rs.1299 Rs.2499 Rs.1799
=== Safe removal via Iterator.remove() ===
Remaining (>= Rs.1000): [1299, 2499, 1799]
Real-World Example - Flipkart Inventory Store and Analytics
A multi-warehouse inventory system stores product stock by warehouse, computes cross-warehouse totals, generates low-stock alerts with a configurable sort order, and groups products by category. Every generic concept from the series appears: nested Map<K, Map<K,V>>, List<? extends Number> for summing, Comparator<? super Product> for flexible sorting, Set<String> with TreeSet for sorted deduplication, and Collections.unmodifiableMap preserving the type parameter through the return path.
1// File: InventoryStore.java
2
3import java.util.*;
4
5public class InventoryStore {
6
7 // Nested generic map: warehouseId -> productId -> quantity
8 private final Map<String, Map<String, Integer>> data = new LinkedHashMap<>();
9
10 public void setStock(String warehouse, String productId, int qty) {
11 data.computeIfAbsent(warehouse, k -> new LinkedHashMap<>())
12 .put(productId, qty);
13 }
14
15 public int getStock(String warehouse, String productId) {
16 return data.getOrDefault(warehouse, Map.of()).getOrDefault(productId, 0);
17 }
18
19 public Map<String, Integer> warehouseView(String warehouse) {
20 // Returns unmodifiable Map<String,Integer> - type parameter preserved
21 return Collections.unmodifiableMap(
22 data.getOrDefault(warehouse, Map.of()));
23 }
24
25 // Set<String> - TreeSet guarantees sorted order and deduplication
26 public Set<String> allProductIds() {
27 Set<String> ids = new TreeSet<>();
28 data.values().forEach(m -> ids.addAll(m.keySet()));
29 return Collections.unmodifiableSet(ids);
30 }
31
32 // List<Integer> per product - one entry per warehouse holding that product
33 public List<Integer> stockAcrossWarehouses(String productId) {
34 List<Integer> quantities = new ArrayList<>();
35 for (Map<String, Integer> warehouseMap : data.values()) {
36 int qty = warehouseMap.getOrDefault(productId, 0);
37 if (qty > 0) quantities.add(qty);
38 }
39 return List.copyOf(quantities);
40 }
41
42 // List<? extends Number> - reads any numeric list using doubleValue()
43 public static double sumStock(List<? extends Number> quantities) {
44 double total = 0;
45 for (Number n : quantities) { // Number - ? extends Number enables this
46 total += n.doubleValue();
47 }
48 return total;
49 }
50}1// File: InventoryAnalytics.java
2
3import java.util.*;
4
5public class InventoryAnalytics {
6
7 private final InventoryStore store;
8 private final Map<String, String> productNames;
9 private final Map<String, String> productCategories;
10 private final Map<String, Double> unitPrices;
11
12 public InventoryAnalytics(InventoryStore store,
13 Map<String, String> productNames,
14 Map<String, String> productCategories,
15 Map<String, Double> unitPrices) {
16 this.store = store;
17 this.productNames = Map.copyOf(productNames);
18 this.productCategories = Map.copyOf(productCategories);
19 this.unitPrices = Map.copyOf(unitPrices);
20 }
21
22 // Low-stock: total below threshold, sorted by Comparator<? super String>
23 // Comparator operates on productId strings - ? super String accepted
24 public List<String> lowStockProducts(int threshold,
25 Comparator<? super String> sortOrder) {
26 List<String> alerts = new ArrayList<>();
27 for (String productId : store.allProductIds()) {
28 double total = InventoryStore.sumStock(store.stockAcrossWarehouses(productId));
29 if (total < threshold) alerts.add(productId);
30 }
31 alerts.sort(sortOrder); // Comparator<? super String>
32 return List.copyOf(alerts);
33 }
34
35 // Total stock value per product: Map<productId, totalValue>
36 public Map<String, Double> stockValueMap() {
37 Map<String, Double> result = new LinkedHashMap<>();
38 for (String productId : store.allProductIds()) {
39 double totalQty = InventoryStore.sumStock(store.stockAcrossWarehouses(productId));
40 double price = unitPrices.getOrDefault(productId, 0.0);
41 result.put(productId, totalQty * price);
42 }
43 return Collections.unmodifiableMap(result);
44 }
45
46 // Group productIds by category: Map<category, List<productId>>
47 public Map<String, List<String>> byCategory() {
48 Map<String, List<String>> groups = new TreeMap<>(); // TreeMap sorts categories
49 for (String productId : store.allProductIds()) {
50 String category = productCategories.getOrDefault(productId, "Uncategorised");
51 groups.computeIfAbsent(category, k -> new ArrayList<>()).add(productId);
52 }
53 return Collections.unmodifiableMap(groups);
54 }
55}1// File: FlipkartInventoryDemo.java
2
3import java.util.*;
4
5public class FlipkartInventoryDemo {
6
7 public static void main(String[] args) {
8
9 InventoryStore store = new InventoryStore();
10
11 store.setStock("WH-NORTH", "P001", 120);
12 store.setStock("WH-NORTH", "P002", 15);
13 store.setStock("WH-NORTH", "P003", 200);
14 store.setStock("WH-NORTH", "P004", 8);
15 store.setStock("WH-NORTH", "P005", 350);
16
17 store.setStock("WH-SOUTH", "P001", 60);
18 store.setStock("WH-SOUTH", "P002", 25);
19 store.setStock("WH-SOUTH", "P003", 5);
20 store.setStock("WH-SOUTH", "P004", 20);
21
22 Map<String, String> names = Map.of(
23 "P001", "Wireless Mouse", "P002", "Laptop Stand",
24 "P003", "USB Hub", "P004", "Keyboard",
25 "P005", "Cable Organiser");
26
27 Map<String, String> categories = Map.of(
28 "P001", "Electronics", "P002", "Accessories",
29 "P003", "Electronics", "P004", "Electronics",
30 "P005", "Accessories");
31
32 Map<String, Double> prices = Map.of(
33 "P001", 799.0, "P002", 1299.0,
34 "P003", 499.0, "P004", 2499.0,
35 "P005", 299.0);
36
37 InventoryAnalytics analytics = new InventoryAnalytics(
38 store, names, categories, prices);
39
40 System.out.println("=== All product IDs (TreeSet - sorted, deduplicated) ===");
41 System.out.println(store.allProductIds());
42
43 System.out.println();
44
45 System.out.println("=== Stock across warehouses (List<? extends Number> summed) ===");
46 for (String id : store.allProductIds()) {
47 List<Integer> perWarehouse = store.stockAcrossWarehouses(id);
48 double total = InventoryStore.sumStock(perWarehouse);
49 System.out.printf(" %-5s %-20s per-warehouse=%s total=%.0f%n",
50 id, names.get(id), perWarehouse, total);
51 }
52
53 System.out.println();
54
55 System.out.println("=== Low stock alerts - threshold 50, sorted by natural order ===");
56 // Comparator.naturalOrder() returns Comparator<String>
57 // satisfies Comparator<? super String>
58 List<String> alerts = analytics.lowStockProducts(50, Comparator.naturalOrder());
59 alerts.forEach(id ->
60 System.out.printf(" ALERT: %-5s %-20s total=%.0f%n",
61 id, names.get(id),
62 InventoryStore.sumStock(store.stockAcrossWarehouses(id))));
63
64 System.out.println();
65
66 System.out.println("=== Low stock sorted by reverse name length (Comparator<Object>) ===");
67 // Comparator<Object> satisfies Comparator<? super String>
68 Comparator<Object> byNameLengthDesc =
69 Comparator.comparing(obj -> -((String) obj).length());
70 List<String> alertsByLength = analytics.lowStockProducts(50, byNameLengthDesc);
71 alertsByLength.forEach(id -> System.out.println(" " + names.get(id)));
72
73 System.out.println();
74
75 System.out.println("=== Stock value per product (Map<String,Double>) ===");
76 analytics.stockValueMap().forEach((id, value) ->
77 System.out.printf(" %-5s %-20s value=Rs.%,.2f%n",
78 id, names.get(id), value));
79
80 System.out.println();
81
82 System.out.println("=== Products by category (TreeMap<String,List<String>>) ===");
83 analytics.byCategory().forEach((category, ids) -> {
84 System.out.println(" " + category + ":");
85 ids.forEach(id -> System.out.println(" " + id + " - " + names.get(id)));
86 });
87 }
88}Output:
=== All product IDs (TreeSet - sorted, deduplicated) ===
[P001, P002, P003, P004, P005]
=== Stock across warehouses (List<? extends Number> summed) ===
P001 Wireless Mouse per-warehouse=[120, 60] total=180
P002 Laptop Stand per-warehouse=[15, 25] total=40
P003 USB Hub per-warehouse=[200, 5] total=205
P004 Keyboard per-warehouse=[8, 20] total=28
P005 Cable Organiser per-warehouse=[350] total=350
=== Low stock alerts - threshold 50, sorted by natural order ===
ALERT: P002 Laptop Stand total=40
ALERT: P004 Keyboard total=28
=== Low stock sorted by reverse name length (Comparator<Object>) ===
Laptop Stand
Keyboard
=== Stock value per product (Map<String,Double>) ===
P001 Wireless Mouse value=Rs.143,820.00
P002 Laptop Stand value=Rs.51,960.00
P003 USB Hub value=Rs.102,295.00
P004 Keyboard value=Rs.69,972.00
P005 Cable Organiser value=Rs.104,650.00
=== Products by category (TreeMap<String,List<String>>) ===
Accessories:
P002 - Laptop Stand
P005 - Cable Organiser
Electronics:
P001 - Wireless Mouse
P003 - USB Hub
P004 - Keyboard
Every concept from the series appears in this one example. Map<String, Map<String, Integer>> is nested generic typing. Set<String> with TreeSet gives sorted deduplication across warehouses. List<? extends Number> in sumStock() reads from any numeric list without code duplication. Comparator<? super String> in lowStockProducts() accepts both specific and general comparators. TreeMap<String, List<String>> in byCategory() keeps categories alphabetically ordered. Collections.unmodifiableMap() and List.copyOf() preserve type parameters through the return boundary.
Generic Concepts in Collections - Quick Reference
| Concept | Where It Appears | Example |
|---|---|---|
Type parameter <E> | Every collection interface and class | List<E>, Set<E>, Queue<E> |
Two type parameters <K,V> | Map types | Map<K,V>, Map.Entry<K,V> |
Bounded <T extends Comparable<T>> | Generic sort/min/max methods | Collections.sort(List<T>) |
Unbounded wildcard ? | Read-only utilities | Collection<?> parameter in frequency() |
Upper wildcard ? extends T | Producer parameters (read from) | addAll(Collection<? extends E>) |
Lower wildcard ? super T | Consumer parameters (write into) | copy(List<? super T> dest, ...) |
| Both wildcards — PECS | Collections.copy() | copy(List<? super T>, List<? extends T>) |
Comparator<? super E> | Sorted collections and sort methods | TreeSet(Comparator<? super E>), sort(Comparator<? super T>) |
Generic Iterator<E> | For-each loop compilation | for (String s : list) |
Diamond <> inference | Every instantiation | new ArrayList<>(), new HashMap<>() |
Best Practices
Always declare collection variables with full type parameters — never raw types. List<String> over List. Map<String, Integer> over Map. Raw types opt out of all compile-time type verification and produce unchecked warnings throughout. Every unchecked warning is a potential runtime ClassCastException that the compiler cannot protect against.
Use the most abstract collection type in method parameters. A method that only iterates should accept Iterable<? extends T> — it works with List, Set, Queue, and any custom iterable. A method that only reads should accept Collection<? extends T>. A method that needs indexed access needs List<T>. The more abstract the parameter, the more call sites the method serves.
Apply PECS to every collection parameter in utility or framework code. For parameters your method reads from, use ? extends T. For parameters your method writes into, use ? super T. For parameters that do both, use a named type parameter <T>. Apply PECS consistently and utility methods become genuinely reusable across the entire type hierarchy they serve.
Return List.copyOf() or Collections.unmodifiableList() from methods that expose internal collections. Both preserve the full generic type — the caller receives List<String>, not List<?> or List<Object>. List.copyOf() is independent of the original; Collections.unmodifiableList() is a live view. Both prevent callers from modifying the internal state through the returned reference.
Common Mistakes
Mistake 1 - Using Raw Collection Types in New Code
1import java.util.ArrayList;
2import java.util.List;
3
4// WRONG - raw List stores Object, retrieval requires cast,
5// wrong-type insertions produce unchecked warnings and ClassCastExceptions
6List products = new ArrayList();
7products.add("Laptop");
8products.add(1299); // silently accepted - different type
9String first = (String) products.get(1); // ClassCastException at runtime
10
11// CORRECT - parameterized type enforces the contract at compile time
12List<String> productNames = new ArrayList<>();
13productNames.add("Laptop");
14// productNames.add(1299); // COMPILE ERROR - caught immediately
15String firstSafe = productNames.get(0); // no cast - compiler verified