1. The Hook (The "Byte-Sized" Intro)
In a Nutshell: Best practices for generics: (1) Avoid raw types (use List<String> not List), (2) Use PECS (Producer Extends, Consumer Super), (3) Prefer lists to arrays (type-safe), (4) Eliminate unchecked warnings with @SuppressWarnings only when safe, (5) Use diamond operator (<>), (6) Program to interfaces (List<T> not ArrayList<T>). Following these prevents ClassCastException and makes APIs flexible.
Think of coding standards in construction. Building codes = best practices. Follow them → safe buildings. Ignore them → disasters!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy: The Safety Checklist
Generics best practices = pilot's pre-flight checklist. Each item prevents a specific failure mode!
3. Technical Mastery (The "Deep Dive")
The 10 Commandments of Generics
- Eliminate raw types
- Use bounded wildcards (PECS)
- Prefer lists to arrays
- Use @SuppressWarnings judiciously
- Use diamond operator
- Consider type inference
- Don't ignore unchecked warnings
- Prefer generic types over raw
- Prefer generic methods over manual typing
- Use
Class<?>for heterogeneous containers
4. Interactive & Applied Code
import java.util.*;
public class GenericsBestPractices {
// ❌ BAD PRACTICE 1: Using raw types
static void badPractice1() {
List list = new ArrayList(); // ❌ Raw type!
list.add("String");
list.add(123); // Mixed types
String s = (String) list.get(0); // Manual cast
}
// ✅ GOOD: Use generics
static void goodPractice1() {
List<String> list = new ArrayList<>(); // ✅ Type-safe
list.add("String");
// list.add(123); // Compile error!
String s = list.get(0); // No cast
}
// ❌ BAD PRACTICE 2: Not using PECS
static void badPractice2() {
// Too restrictive - only accepts List<Number>
void processNumbers(List<Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
// List<Integer> ints = Arrays.asList(1, 2);
// processNumbers(ints); // ❌ Compile error!
}
// ✅ GOOD: Use PECS (Producer Extends)
static void processNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n.doubleValue());
}
// Accepts List<Integer>, List<Double>, etc.
}
// ❌ BAD PRACTICE 3: Using arrays with generics
static void badPractice3() {
// Arrays are covariant, generics are invariant
Object[] objArray = new String[10]; // ✅ OK (covariant)
objArray[0] = 123; // ❌ Runtime error!
// Can't even create generic array
// List<String>[] array = new List<String>[10]; // Compile error
}
// ✅ GOOD: Use List of Lists
static void goodPractice3() {
List<List<String>> listOfLists = new ArrayList<>();
listOfLists.add(new ArrayList<>());
listOfLists.get(0).add("Safe!");
}
// ✅ GOOD PRACTICE 4: Judicious use of @SuppressWarnings
@SuppressWarnings("unchecked") // Document WHY it's safe
static <T> T[] toArray(List<T> list, Class<T> clazz) {
T[] array = (T[]) java.lang.reflect.Array.newInstance(clazz, list.size());
return list.toArray(array);
}
// ✅ GOOD PRACTICE 5: Diamond operator
static void goodPractice5() {
// ❌ OLD
Map<String, List<Integer>> map1 =
new HashMap<String, List<Integer>>();
// ✅ NEW
Map<String, List<Integer>> map2 = new HashMap<>();
}
// ✅ GOOD PRACTICE 6: Generic methods
static <T> void swap(List<T> list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
// ✅ GOOD PRACTICE 7: Bounded wildcards for max flexibility
static double sum(Collection<? extends Number> numbers) {
double result = 0.0;
for (Number num : numbers) {
result += num.doubleValue();
}
return result;
}
static void addIntegers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
// ✅ GOOD PRACTICE 8: Return concrete types, accept interfaces
static List<String> createList() {
return new ArrayList<>(); // Return interface type
}
static void processList(List<String> list) {
// Accept interface, can pass ArrayList, LinkedList, etc.
}
// ✅ GOOD PRACTICE 9: Heterogeneous container with Class<?>
static class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
favorites.put(type, instance);
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> type) {
return (T) favorites.get(type);
}
}
public static void main(String[] args) {
// Demonstrate best practices
goodPractice1();
List<Integer> ints = Arrays.asList(1, 2, 3);
processNumbers(ints); // PECS allows this
goodPractice3();
List<String> names = new ArrayList<>(Arrays.asList("Bob", "Alice"));
swap(names, 0, 1);
System.out.println("Swapped: " + names);
// Heterogeneous container
Favorites favorites = new Favorites();
favorites.put(String.class, "Hello");
favorites.put(Integer.class, 42);
String s = favorites.get(String.class);
Integer i = favorites.get(Integer.class);
System.out.println(s + " " + i);
}
}5. The Comparison & Decision Layer
Quick Reference Guide
| Scenario | Bad | Good |
|---|---|---|
| Collection variable | List list | List<String> list |
| Reading from collection | List<Number> | List<? extends Number> |
| Writing to collection | List<Number> | List<? super Integer> |
| Array vs List | List<T>[] | List<List<T>> |
| Type specification | new ArrayList<String>() | new ArrayList<>() |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "What's wrong with using raw types in new code?" Answer: Raw types lose all type safety benefits:
// ❌ RAW TYPE
List list = new ArrayList();
list.add("String");
list.add(123); // No compile error!
String s = (String) list.get(1); // Runtime ClassCastException!
// ✅ GENERIC TYPE
List<String> list = new ArrayList<>();
list.add("String");
// list.add(123); // Compile error prevents bug!
String s = list.get(0); // Type-safe, no castWhy raw types exist? Backward compatibility with pre-Java 5 code. Never use in new code!
Pro-Tips:
- PECS mnemonic (Effective Java):
// Producer Extends - getting items OUT
public void processAll(List<? extends T> producer) {
for (T item : producer) { /* read */ }
}
// Consumer Super - putting items IN
public void addAll(List<? super T> consumer) {
consumer.add(item); /* write */
}- Recursive type bound for comparable:
public static <T extends Comparable<T>> T max(List<T> list) {
// T can compare to itself!
}- Prefer
List.of()overArrays.asList()(Java 9+):
// OLD
List<String> list = Arrays.asList("A", "B"); // Fixed-size
// NEW
List<String> list = List.of("A", "B"); // Immutable, compact