Lesson Completion
Back to course

Generics Best Practices: Writing Clean, Safe Code

Advanced
15 minutes4.8Java

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

  1. Eliminate raw types
  2. Use bounded wildcards (PECS)
  3. Prefer lists to arrays
  4. Use @SuppressWarnings judiciously
  5. Use diamond operator
  6. Consider type inference
  7. Don't ignore unchecked warnings
  8. Prefer generic types over raw
  9. Prefer generic methods over manual typing
  10. Use Class<?> for heterogeneous containers

4. Interactive & Applied Code

java
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

ScenarioBadGood
Collection variableList listList<String> list
Reading from collectionList<Number>List<? extends Number>
Writing to collectionList<Number>List<? super Integer>
Array vs ListList<T>[]List<List<T>>
Type specificationnew 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:

java
// ❌ 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 cast

Why raw types exist? Backward compatibility with pre-Java 5 code. Never use in new code!

Pro-Tips:

  1. PECS mnemonic (Effective Java):
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 */ }
  1. Recursive type bound for comparable:
java
public static <T extends Comparable<T>> T max(List<T> list) { // T can compare to itself! }
  1. Prefer List.of() over Arrays.asList() (Java 9+):
java
// OLD List<String> list = Arrays.asList("A", "B"); // Fixed-size // NEW List<String> list = List.of("A", "B"); // Immutable, compact

Topics Covered

Java FundamentalsAdvanced Java

Tags

#java#generics#type-safety#reusability

Last Updated

2025-02-01