Your method accepts List<Number> but caller has List<Integer>. It won't compile - generics aren't covariant. Wildcards like List<? extends Number> accept List of any Number subtype, enabling flexible APIs.

Unbounded wildcard

Accept any type.

Unbounded.java
import java.util.*;

public class Unbounded {
    public static void printList(List<?> list) {
        System.out.print("  List: [");
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) System.out.print(", ");
            System.out.print(list.get(i));
        }
        System.out.println("]");
    }
    
    public static void main(String[] args) {
        System.out.println("Unbounded wildcard:\n");
        
        List<Integer> ints = Arrays.asList(1, 2, 3);
        List<String> strs = Arrays.asList("a", "b", "c");
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
        
        printList(ints);
        printList(strs);
        printList(doubles);
        
        // <?> means "list of unknown type"
        // Can read elements as Object
        // Cannot add elements (except null)
        // Useful for methods that only read
        
        System.out.println("\nSize check:");
        
        class Utils {
            public static int getSize(List<?> list) {
                return list.size();
            }
            
            public static boolean isEmpty(List<?> list) {
                return list.isEmpty();
            }
        }
        
        System.out.println("  Size of ints: " + Utils.getSize(ints));
        System.out.println("  Size of strs: " + Utils.getSize(strs));
        System.out.println("  Empty: " + Utils.isEmpty(new ArrayList<>()));
        
        System.out.println("\nClear list:");
        
        class Clearer {
            public static void clear(List<?> list) {
                list.clear();
                System.out.println("  List cleared, size: " + list.size());
            }
        }
        
        List<String> temp = new ArrayList<>(Arrays.asList("x", "y", "z"));
        System.out.println("  Before: " + temp);
        Clearer.clear(temp);
        System.out.println("  After: " + temp);
        
        System.out.println("\nContains check:");
        
        class Checker {
            public static boolean contains(List<?> list, Object item) {
                return list.contains(item);
            }
            
            public static void displayFirst(List<?> list) {
                if (!list.isEmpty()) {
                    Object first = list.get(0);  // Read as Object
                    System.out.println("  First: " + first);
                }
            }
        }
        
        int intSearch = ;
        System.out.println("  ints contains " + intSearch + ": " +
                         Checker.contains(ints, intSearch));
        System.out.println("  strs contains 'x': " + Checker.contains(strs, "x"));
        
        Checker.displayFirst(ints);
        Checker.displayFirst(strs);
        
        System.out.println("\nCopy to Object list:");
        
        class Copier {
            public static List<Object> toObjectList(List<?> source) {
                List<Object> result = new ArrayList<>();
                for (Object item : source) {  // Can read as Object
                    result.add(item);
                }
                return result;
            }
        }
        
        List<Integer> numbers = Arrays.asList(10, 20, 30);
        List<Object> objects = Copier.toObjectList(numbers);
        System.out.println("  Copied: " + objects);
        
        System.out.println("\nLimitations:");
        
        List<?> wildList = new ArrayList<String>();
        // wildList.add("text");  // Compile error - can't add
        // wildList.add(123);     // Compile error - can't add
        wildList.add(null);      // Only null allowed
        
        System.out.println("  Can only add null to List<?>");
        System.out.println("  Size: " + wildList.size());
    }
}
import java.util.*;

public class Unbounded {
    public static void printList(List<?> list) {
        System.out.print("  List: [");
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) System.out.print(", ");
            System.out.print(list.get(i));
        }
        System.out.println("]");
    }
    
    public static void main(String[] args) {
        System.out.println("Unbounded wildcard:\n");
        
        List<Integer> ints = Arrays.asList(1, 2, 3);
        List<String> strs = Arrays.asList("a", "b", "c");
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
        
        printList(ints);
        printList(strs);
        printList(doubles);
        
        // <?> means "list of unknown type"
        // Can read elements as Object
        // Cannot add elements (except null)
        // Useful for methods that only read
        
        System.out.println("\nSize check:");
        
        class Utils {
            public static int getSize(List<?> list) {
                return list.size();
            }
            
            public static boolean isEmpty(List<?> list) {
                return list.isEmpty();
            }
        }
        
        System.out.println("  Size of ints: " + Utils.getSize(ints));
        System.out.println("  Size of strs: " + Utils.getSize(strs));
        System.out.println("  Empty: " + Utils.isEmpty(new ArrayList<>()));
        
        System.out.println("\nClear list:");
        
        class Clearer {
            public static void clear(List<?> list) {
                list.clear();
                System.out.println("  List cleared, size: " + list.size());
            }
        }
        
        List<String> temp = new ArrayList<>(Arrays.asList("x", "y", "z"));
        System.out.println("  Before: " + temp);
        Clearer.clear(temp);
        System.out.println("  After: " + temp);
        
        System.out.println("\nContains check:");
        
        class Checker {
            public static boolean contains(List<?> list, Object item) {
                return list.contains(item);
            }
            
            public static void displayFirst(List<?> list) {
                if (!list.isEmpty()) {
                    Object first = list.get(0);  // Read as Object
                    System.out.println("  First: " + first);
                }
            }
        }
        
        int intSearch = ;
        System.out.println("  ints contains " + intSearch + ": " +
                         Checker.contains(ints, intSearch));
        System.out.println("  strs contains 'x': " + Checker.contains(strs, "x"));
        
        Checker.displayFirst(ints);
        Checker.displayFirst(strs);
        
        System.out.println("\nCopy to Object list:");
        
        class Copier {
            public static List<Object> toObjectList(List<?> source) {
                List<Object> result = new ArrayList<>();
                for (Object item : source) {  // Can read as Object
                    result.add(item);
                }
                return result;
            }
        }
        
        List<Integer> numbers = Arrays.asList(10, 20, 30);
        List<Object> objects = Copier.toObjectList(numbers);
        System.out.println("  Copied: " + objects);
        
        System.out.println("\nLimitations:");
        
        List<?> wildList = new ArrayList<String>();
        // wildList.add("text");  // Compile error - can't add
        // wildList.add(123);     // Compile error - can't add
        wildList.add(null);      // Only null allowed
        
        System.out.println("  Can only add null to List<?>");
        System.out.println("  Size: " + wildList.size());
    }
}
import java.util.*;

public class Unbounded {
    public static void printList(List<?> list) {
        System.out.print("  List: [");
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) System.out.print(", ");
            System.out.print(list.get(i));
        }
        System.out.println("]");
    }
    
    public static void main(String[] args) {
        System.out.println("Unbounded wildcard:\n");
        
        List<Integer> ints = Arrays.asList(1, 2, 3);
        List<String> strs = Arrays.asList("a", "b", "c");
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
        
        printList(ints);
        printList(strs);
        printList(doubles);
        
        // <?> means "list of unknown type"
        // Can read elements as Object
        // Cannot add elements (except null)
        // Useful for methods that only read
        
        System.out.println("\nSize check:");
        
        class Utils {
            public static int getSize(List<?> list) {
                return list.size();
            }
            
            public static boolean isEmpty(List<?> list) {
                return list.isEmpty();
            }
        }
        
        System.out.println("  Size of ints: " + Utils.getSize(ints));
        System.out.println("  Size of strs: " + Utils.getSize(strs));
        System.out.println("  Empty: " + Utils.isEmpty(new ArrayList<>()));
        
        System.out.println("\nClear list:");
        
        class Clearer {
            public static void clear(List<?> list) {
                list.clear();
                System.out.println("  List cleared, size: " + list.size());
            }
        }
        
        List<String> temp = new ArrayList<>(Arrays.asList("x", "y", "z"));
        System.out.println("  Before: " + temp);
        Clearer.clear(temp);
        System.out.println("  After: " + temp);
        
        System.out.println("\nContains check:");
        
        class Checker {
            public static boolean contains(List<?> list, Object item) {
                return list.contains(item);
            }
            
            public static void displayFirst(List<?> list) {
                if (!list.isEmpty()) {
                    Object first = list.get(0);  // Read as Object
                    System.out.println("  First: " + first);
                }
            }
        }
        
        int intSearch = ;
        System.out.println("  ints contains " + intSearch + ": " +
                         Checker.contains(ints, intSearch));
        System.out.println("  strs contains 'x': " + Checker.contains(strs, "x"));
        
        Checker.displayFirst(ints);
        Checker.displayFirst(strs);
        
        System.out.println("\nCopy to Object list:");
        
        class Copier {
            public static List<Object> toObjectList(List<?> source) {
                List<Object> result = new ArrayList<>();
                for (Object item : source) {  // Can read as Object
                    result.add(item);
                }
                return result;
            }
        }
        
        List<Integer> numbers = Arrays.asList(10, 20, 30);
        List<Object> objects = Copier.toObjectList(numbers);
        System.out.println("  Copied: " + objects);
        
        System.out.println("\nLimitations:");
        
        List<?> wildList = new ArrayList<String>();
        // wildList.add("text");  // Compile error - can't add
        // wildList.add(123);     // Compile error - can't add
        wildList.add(null);      // Only null allowed
        
        System.out.println("  Can only add null to List<?>");
        System.out.println("  Size: " + wildList.size());
    }
}

List<?> accepts List of any type. Can only read as Object.

unbounded wildcard `<?>` matches any type. Read-only for practical purposes.

Upper bounded wildcard

Accept type or subtypes.

UpperBound.java
import java.util.*;

public class UpperBound {
    public static double sum(List<? extends Number> numbers) {
        double total = 0;
        for (Number num : numbers) {  // Can read as Number
            total += num.doubleValue();
        }
        return total;
    }
    
    public static void main(String[] args) {
        System.out.println("Upper bounded wildcard:\n");
        
        List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);
        List<Long> longs = Arrays.asList(10L, 20L, 30L);
        
        System.out.println("  Sum ints: " + sum(ints));
        System.out.println("  Sum doubles: " + sum(doubles));
        System.out.println("  Sum longs: " + sum(longs));
        
        // <? extends T> means "unknown type that extends T"
        // Can read elements as T
        // Cannot add elements (except null) - don't know exact type
        // PECS: Producer Extends - use when reading/producing
        
        System.out.println("\nFind max:");
        
        class Finder {
            public static <T extends Comparable<T>> T max(
                    List<? extends T> list) {
                if (list.isEmpty()) {
                    return null;
                }
                
                T max = list.get(0);
                for (T item : list) {
                    if (item.compareTo(max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        System.out.println("  Max int: " + Finder.max(ints));
        System.out.println("  Max double: " + Finder.max(doubles));
        
        List<String> words = Arrays.asList("apple", "zebra", "banana");
        System.out.println("  Max string: " + Finder.max(words));
        
        System.out.println("\nCopy numbers:");
        
        class Copier {
            public static void copyNumbers(
                    List<? extends Number> source,
                    List<Number> dest) {
                dest.clear();
                for (Number num : source) {  // Read as Number
                    dest.add(num);
                }
            }
        }
        
        List<Number> result = new ArrayList<>();
        Copier.copyNumbers(ints, result);
        System.out.println("  Copied: " + result);
        
        System.out.println("\nAverage:");
        
        class Stats {
            public static double average(List<? extends Number> numbers) {
                if (numbers.isEmpty()) {
                    return 0;
                }
                return sum(numbers) / numbers.size();
            }
            
            public static Number min(List<? extends Number> numbers) {
                if (numbers.isEmpty()) {
                    return null;
                }
                
                double minVal = numbers.get(0).doubleValue();
                Number min = numbers.get(0);
                
                for (Number num : numbers) {
                    if (num.doubleValue() < minVal) {
                        minVal = num.doubleValue();
                        min = num;
                    }
                }
                return min;
            }
        }
        
        System.out.println("  Average: " + Stats.average(ints));
        System.out.println("  Min: " + Stats.min(doubles));
        
        System.out.println("\nPrint comparable:");
        
        class Printer {
            public static <T extends Comparable<T>> void printSorted(
                    List<? extends T> list) {
                List<T> copy = new ArrayList<>(list);
                Collections.sort(copy);
                System.out.print("  Sorted: ");
                for (T item : copy) {
                    System.out.print(item + " ");
                }
                System.out.println();
            }
        }
        
        Printer.printSorted(Arrays.asList(5, 1, 9, 3, 7));
        Printer.printSorted(Arrays.asList("dog", "cat", "ant", "bee"));
        
        System.out.println("\nLimitations:");
        
        List<? extends Number> numList = new ArrayList<Integer>();
        // numList.add(1);        // Compile error
        // numList.add(1.5);      // Compile error
        // numList.add(new Integer(1));  // Compile error
        
        System.out.println("  Cannot add to List<? extends Number>");
        System.out.println("  Reason: Don't know if it's Integer, Double, etc.");
    }
}

List<? extends Number> - accepts List, List, etc.

upper bounded wildcard `<? extends T>` - producer. Read as T, can't add (except null).

Lower bounded wildcard

Accept type or supertypes.

LowerBound.java
import java.util.*;

public class LowerBound {
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);      // Can add Integer
        list.add(2);
        list.add(3);
        System.out.println("  Added integers: " + list);
    }
    
    public static void main(String[] args) {
        System.out.println("Lower bounded wildcard:\n");
        
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = new ArrayList<>();
        List<Object> objList = new ArrayList<>();
        
        addIntegers(intList);
        addIntegers(numList);
        addIntegers(objList);
        
        // <? super T> means "unknown type that is T or superclass"
        // Can add T elements
        // Can read as Object only (don't know exact type)
        // PECS: Consumer Super - use when writing/consuming
        
        System.out.println("\nAdd all:");
        
        class Adder {
            public static <T> void addAll(
                    List<? super T> dest,
                    List<? extends T> src) {
                for (T item : src) {
                    dest.add(item);  // Can add T to super T
                }
            }
        }
        
        List<Number> numbers = new ArrayList<>();
        List<Integer> ints = Arrays.asList(10, 20, 30);
        List<Double> doubles = Arrays.asList(1.5, 2.5);
        
        Adder.addAll(numbers, ints);
        Adder.addAll(numbers, doubles);
        
        System.out.println("  Combined: " + numbers);
        
        System.out.println("\nFill list:");
        
        class Filler {
            public static <T> void fill(List<? super T> list, T value, int count) {
                for (int i = 0; i < count; i++) {
                    list.add(value);
                }
            }
        }
        
        List<Object> objects = new ArrayList<>();
        int fillCount = ;
        Filler.fill(objects, "hello", fillCount);
        Filler.fill(objects, 42, 2);
        
        System.out.println("  Filled: " + objects);
        
        System.out.println("\nCopy method:");
        
        class Copier {
            public static <T> void copy(
                    List<? super T> dest,
                    List<? extends T> src) {
                dest.clear();
                for (T item : src) {
                    dest.add(item);
                }
            }
        }
        
        List<String> source = Arrays.asList("a", "b", "c");
        List<Object> destination = new ArrayList<>();
        
        Copier.copy(destination, source);
        System.out.println("  Copied: " + destination);
        
        System.out.println("\nAppend:");
        
        class Appender {
            public static void appendIntegers(List<? super Integer> list) {
                for (int i = 1; i <= 5; i++) {
                    list.add(i);
                }
            }
        }
        
        List<Number> nums = new ArrayList<>();
        Appender.appendIntegers(nums);
        System.out.println("  Numbers: " + nums);
        
        List<Object> objs = new ArrayList<>();
        Appender.appendIntegers(objs);
        System.out.println("  Objects: " + objs);
        
        System.out.println("\nReading limitations:");
        
        List<? super Integer> superList = new ArrayList<Number>();
        superList.add(10);
        superList.add(20);
        
        // Can only read as Object
        Object first = superList.get(0);  // Only Object
        // Integer val = superList.get(0);  // Compile error
        
        System.out.println("  First (as Object): " + first);
        System.out.println("  Can only read as Object from List<? super Integer>");
        
        System.out.println("\nPECS example:");
        
        class PecsDemo {
            // Producer Extends - reading from source
            // Consumer Super - writing to destination
            public static <T> void transfer(
                    List<? super T> dest,      // Consumer - can add T
                    List<? extends T> src) {   // Producer - can read T
                for (T item : src) {
                    dest.add(item);
                }
            }
        }
        
        List<Integer> intSource = Arrays.asList(1, 2, 3);
        List<Number> numDest = new ArrayList<>();
        
        PecsDemo.transfer(numDest, intSource);
        System.out.println("  Transferred: " + numDest);
    }
}
import java.util.*;

public class LowerBound {
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);      // Can add Integer
        list.add(2);
        list.add(3);
        System.out.println("  Added integers: " + list);
    }
    
    public static void main(String[] args) {
        System.out.println("Lower bounded wildcard:\n");
        
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = new ArrayList<>();
        List<Object> objList = new ArrayList<>();
        
        addIntegers(intList);
        addIntegers(numList);
        addIntegers(objList);
        
        // <? super T> means "unknown type that is T or superclass"
        // Can add T elements
        // Can read as Object only (don't know exact type)
        // PECS: Consumer Super - use when writing/consuming
        
        System.out.println("\nAdd all:");
        
        class Adder {
            public static <T> void addAll(
                    List<? super T> dest,
                    List<? extends T> src) {
                for (T item : src) {
                    dest.add(item);  // Can add T to super T
                }
            }
        }
        
        List<Number> numbers = new ArrayList<>();
        List<Integer> ints = Arrays.asList(10, 20, 30);
        List<Double> doubles = Arrays.asList(1.5, 2.5);
        
        Adder.addAll(numbers, ints);
        Adder.addAll(numbers, doubles);
        
        System.out.println("  Combined: " + numbers);
        
        System.out.println("\nFill list:");
        
        class Filler {
            public static <T> void fill(List<? super T> list, T value, int count) {
                for (int i = 0; i < count; i++) {
                    list.add(value);
                }
            }
        }
        
        List<Object> objects = new ArrayList<>();
        int fillCount = ;
        Filler.fill(objects, "hello", fillCount);
        Filler.fill(objects, 42, 2);
        
        System.out.println("  Filled: " + objects);
        
        System.out.println("\nCopy method:");
        
        class Copier {
            public static <T> void copy(
                    List<? super T> dest,
                    List<? extends T> src) {
                dest.clear();
                for (T item : src) {
                    dest.add(item);
                }
            }
        }
        
        List<String> source = Arrays.asList("a", "b", "c");
        List<Object> destination = new ArrayList<>();
        
        Copier.copy(destination, source);
        System.out.println("  Copied: " + destination);
        
        System.out.println("\nAppend:");
        
        class Appender {
            public static void appendIntegers(List<? super Integer> list) {
                for (int i = 1; i <= 5; i++) {
                    list.add(i);
                }
            }
        }
        
        List<Number> nums = new ArrayList<>();
        Appender.appendIntegers(nums);
        System.out.println("  Numbers: " + nums);
        
        List<Object> objs = new ArrayList<>();
        Appender.appendIntegers(objs);
        System.out.println("  Objects: " + objs);
        
        System.out.println("\nReading limitations:");
        
        List<? super Integer> superList = new ArrayList<Number>();
        superList.add(10);
        superList.add(20);
        
        // Can only read as Object
        Object first = superList.get(0);  // Only Object
        // Integer val = superList.get(0);  // Compile error
        
        System.out.println("  First (as Object): " + first);
        System.out.println("  Can only read as Object from List<? super Integer>");
        
        System.out.println("\nPECS example:");
        
        class PecsDemo {
            // Producer Extends - reading from source
            // Consumer Super - writing to destination
            public static <T> void transfer(
                    List<? super T> dest,      // Consumer - can add T
                    List<? extends T> src) {   // Producer - can read T
                for (T item : src) {
                    dest.add(item);
                }
            }
        }
        
        List<Integer> intSource = Arrays.asList(1, 2, 3);
        List<Number> numDest = new ArrayList<>();
        
        PecsDemo.transfer(numDest, intSource);
        System.out.println("  Transferred: " + numDest);
    }
}
import java.util.*;

public class LowerBound {
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);      // Can add Integer
        list.add(2);
        list.add(3);
        System.out.println("  Added integers: " + list);
    }
    
    public static void main(String[] args) {
        System.out.println("Lower bounded wildcard:\n");
        
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = new ArrayList<>();
        List<Object> objList = new ArrayList<>();
        
        addIntegers(intList);
        addIntegers(numList);
        addIntegers(objList);
        
        // <? super T> means "unknown type that is T or superclass"
        // Can add T elements
        // Can read as Object only (don't know exact type)
        // PECS: Consumer Super - use when writing/consuming
        
        System.out.println("\nAdd all:");
        
        class Adder {
            public static <T> void addAll(
                    List<? super T> dest,
                    List<? extends T> src) {
                for (T item : src) {
                    dest.add(item);  // Can add T to super T
                }
            }
        }
        
        List<Number> numbers = new ArrayList<>();
        List<Integer> ints = Arrays.asList(10, 20, 30);
        List<Double> doubles = Arrays.asList(1.5, 2.5);
        
        Adder.addAll(numbers, ints);
        Adder.addAll(numbers, doubles);
        
        System.out.println("  Combined: " + numbers);
        
        System.out.println("\nFill list:");
        
        class Filler {
            public static <T> void fill(List<? super T> list, T value, int count) {
                for (int i = 0; i < count; i++) {
                    list.add(value);
                }
            }
        }
        
        List<Object> objects = new ArrayList<>();
        int fillCount = ;
        Filler.fill(objects, "hello", fillCount);
        Filler.fill(objects, 42, 2);
        
        System.out.println("  Filled: " + objects);
        
        System.out.println("\nCopy method:");
        
        class Copier {
            public static <T> void copy(
                    List<? super T> dest,
                    List<? extends T> src) {
                dest.clear();
                for (T item : src) {
                    dest.add(item);
                }
            }
        }
        
        List<String> source = Arrays.asList("a", "b", "c");
        List<Object> destination = new ArrayList<>();
        
        Copier.copy(destination, source);
        System.out.println("  Copied: " + destination);
        
        System.out.println("\nAppend:");
        
        class Appender {
            public static void appendIntegers(List<? super Integer> list) {
                for (int i = 1; i <= 5; i++) {
                    list.add(i);
                }
            }
        }
        
        List<Number> nums = new ArrayList<>();
        Appender.appendIntegers(nums);
        System.out.println("  Numbers: " + nums);
        
        List<Object> objs = new ArrayList<>();
        Appender.appendIntegers(objs);
        System.out.println("  Objects: " + objs);
        
        System.out.println("\nReading limitations:");
        
        List<? super Integer> superList = new ArrayList<Number>();
        superList.add(10);
        superList.add(20);
        
        // Can only read as Object
        Object first = superList.get(0);  // Only Object
        // Integer val = superList.get(0);  // Compile error
        
        System.out.println("  First (as Object): " + first);
        System.out.println("  Can only read as Object from List<? super Integer>");
        
        System.out.println("\nPECS example:");
        
        class PecsDemo {
            // Producer Extends - reading from source
            // Consumer Super - writing to destination
            public static <T> void transfer(
                    List<? super T> dest,      // Consumer - can add T
                    List<? extends T> src) {   // Producer - can read T
                for (T item : src) {
                    dest.add(item);
                }
            }
        }
        
        List<Integer> intSource = Arrays.asList(1, 2, 3);
        List<Number> numDest = new ArrayList<>();
        
        PecsDemo.transfer(numDest, intSource);
        System.out.println("  Transferred: " + numDest);
    }
}

List<? super Integer> - accepts List, List, List.

lower bounded wildcard `<? super T>` - consumer. Can add T, read only as Object.

PECS principle

Producer Extends, Consumer Super.

Pecs.java
import java.util.*;

public class Pecs {
    static class Stack<E> {
        private List<E> elements = new ArrayList<>();
        
        public void push(E item) {
            elements.add(item);
        }
        
        public E pop() {
            if (elements.isEmpty()) {
                return null;
            }
            return elements.remove(elements.size() - 1);
        }
        
        // Producer - pushes items from src to this stack
        public void pushAll(Iterable<? extends E> src) {
            for (E item : src) {  // Reading from producer
                push(item);
            }
        }
        
        // Consumer - pops items from this stack to dst
        public void popAll(Collection<? super E> dst) {
            while (!elements.isEmpty()) {
                dst.add(pop());  // Writing to consumer
            }
        }
        
        public int size() {
            return elements.size();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("PECS principle:\n");
        
        Stack<Number> numberStack = new Stack<>();
        
        // Producer Extends - can push from Integer list
        List<Integer> ints = Arrays.asList(1, 2, 3);
        numberStack.pushAll(ints);
        
        System.out.println("  Pushed integers, size: " + numberStack.size());
        
        // Consumer Super - can pop to Object list
        List<Object> objects = new ArrayList<>();
        numberStack.popAll(objects);
        
        System.out.println("  Popped to objects: " + objects);
        
        // PECS: Producer Extends, Consumer Super
        // Producer (reading): use <? extends T>
        // Consumer (writing): use <? super T>
        // Maximizes flexibility while maintaining type safety
        
        System.out.println("\nCollection utilities:");
        
        class CollectionUtils {
            // Producer - reading from source
            public static <T> void copy(
                    List<? super T> dest,      // Consumer - writing
                    List<? extends T> src) {   // Producer - reading
                dest.clear();
                for (T item : src) {
                    dest.add(item);
                }
            }
            
            // Producer - reading from multiple sources
            public static <T> void addAll(
                    Collection<? super T> dest,  // Consumer
                    Collection<? extends T>... sources) {  // Producers
                for (Collection<? extends T> src : sources) {
                    for (T item : src) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.5, 2.5);
        List<Number> numberList = new ArrayList<>();
        
        CollectionUtils.addAll(numberList, intList, doubleList);
        System.out.println("  Combined: " + numberList);
        
        System.out.println("\nFrequency counter:");
        
        class Counter {
            public static <T> int frequency(
                    Collection<? extends T> collection,  // Producer
                    T target) {
                int count = 0;
                for (T item : collection) {  // Reading
                    if (item != null && item.equals(target)) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 1, 2);
        int frequencyTarget = ;
        System.out.println("  Frequency of " + frequencyTarget + ": " +
                         Counter.frequency(numbers, frequencyTarget));
        
        System.out.println("\nMax with comparator:");
        
        class MaxFinder {
            public static <T> T max(
                    Collection<? extends T> coll,  // Producer - reading
                    Comparator<? super T> comp) {  // Consumer - comparing
                if (coll.isEmpty()) {
                    return null;
                }
                
                Iterator<? extends T> it = coll.iterator();
                T max = it.next();
                
                while (it.hasNext()) {
                    T item = it.next();
                    if (comp.compare(item, max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        List<String> words = Arrays.asList("apple", "zoo", "banana");
        String maxWord = MaxFinder.max(words, String::compareTo);
        System.out.println("  Max word: " + maxWord);
        
        System.out.println("\nFilter:");
        
        interface Predicate<T> {
            boolean test(T item);
        }
        
        class Filter {
            public static <T> void removeIf(
                    Collection<? extends T> source,   // Producer - reading
                    Collection<? super T> dest,       // Consumer - writing
                    Predicate<? super T> filter) {    // Consumer - testing
                for (T item : source) {
                    if (!filter.test(item)) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Number> evens = new ArrayList<>();
        
        Filter.removeIf(nums, evens, n -> n % 2 != 0);  // Keep evens
        System.out.println("  Even numbers: " + evens);
        
        System.out.println("\nMap transformation:");
        
        interface Function<T, R> {
            R apply(T item);
        }
        
        class Mapper {
            public static <T, R> void map(
                    Collection<? extends T> source,  // Producer
                    Collection<? super R> dest,      // Consumer
                    Function<? super T, ? extends R> mapper) {
                for (T item : source) {
                    R result = mapper.apply(item);
                    dest.add(result);
                }
            }
        }
        
        List<String> strings = Arrays.asList("1", "2", "3");
        List<Object> parsed = new ArrayList<>();
        
        Mapper.map(strings, parsed, Integer::parseInt);
        System.out.println("  Parsed: " + parsed);
    }
}
import java.util.*;

public class Pecs {
    static class Stack<E> {
        private List<E> elements = new ArrayList<>();
        
        public void push(E item) {
            elements.add(item);
        }
        
        public E pop() {
            if (elements.isEmpty()) {
                return null;
            }
            return elements.remove(elements.size() - 1);
        }
        
        // Producer - pushes items from src to this stack
        public void pushAll(Iterable<? extends E> src) {
            for (E item : src) {  // Reading from producer
                push(item);
            }
        }
        
        // Consumer - pops items from this stack to dst
        public void popAll(Collection<? super E> dst) {
            while (!elements.isEmpty()) {
                dst.add(pop());  // Writing to consumer
            }
        }
        
        public int size() {
            return elements.size();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("PECS principle:\n");
        
        Stack<Number> numberStack = new Stack<>();
        
        // Producer Extends - can push from Integer list
        List<Integer> ints = Arrays.asList(1, 2, 3);
        numberStack.pushAll(ints);
        
        System.out.println("  Pushed integers, size: " + numberStack.size());
        
        // Consumer Super - can pop to Object list
        List<Object> objects = new ArrayList<>();
        numberStack.popAll(objects);
        
        System.out.println("  Popped to objects: " + objects);
        
        // PECS: Producer Extends, Consumer Super
        // Producer (reading): use <? extends T>
        // Consumer (writing): use <? super T>
        // Maximizes flexibility while maintaining type safety
        
        System.out.println("\nCollection utilities:");
        
        class CollectionUtils {
            // Producer - reading from source
            public static <T> void copy(
                    List<? super T> dest,      // Consumer - writing
                    List<? extends T> src) {   // Producer - reading
                dest.clear();
                for (T item : src) {
                    dest.add(item);
                }
            }
            
            // Producer - reading from multiple sources
            public static <T> void addAll(
                    Collection<? super T> dest,  // Consumer
                    Collection<? extends T>... sources) {  // Producers
                for (Collection<? extends T> src : sources) {
                    for (T item : src) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.5, 2.5);
        List<Number> numberList = new ArrayList<>();
        
        CollectionUtils.addAll(numberList, intList, doubleList);
        System.out.println("  Combined: " + numberList);
        
        System.out.println("\nFrequency counter:");
        
        class Counter {
            public static <T> int frequency(
                    Collection<? extends T> collection,  // Producer
                    T target) {
                int count = 0;
                for (T item : collection) {  // Reading
                    if (item != null && item.equals(target)) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 1, 2);
        int frequencyTarget = ;
        System.out.println("  Frequency of " + frequencyTarget + ": " +
                         Counter.frequency(numbers, frequencyTarget));
        
        System.out.println("\nMax with comparator:");
        
        class MaxFinder {
            public static <T> T max(
                    Collection<? extends T> coll,  // Producer - reading
                    Comparator<? super T> comp) {  // Consumer - comparing
                if (coll.isEmpty()) {
                    return null;
                }
                
                Iterator<? extends T> it = coll.iterator();
                T max = it.next();
                
                while (it.hasNext()) {
                    T item = it.next();
                    if (comp.compare(item, max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        List<String> words = Arrays.asList("apple", "zoo", "banana");
        String maxWord = MaxFinder.max(words, String::compareTo);
        System.out.println("  Max word: " + maxWord);
        
        System.out.println("\nFilter:");
        
        interface Predicate<T> {
            boolean test(T item);
        }
        
        class Filter {
            public static <T> void removeIf(
                    Collection<? extends T> source,   // Producer - reading
                    Collection<? super T> dest,       // Consumer - writing
                    Predicate<? super T> filter) {    // Consumer - testing
                for (T item : source) {
                    if (!filter.test(item)) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Number> evens = new ArrayList<>();
        
        Filter.removeIf(nums, evens, n -> n % 2 != 0);  // Keep evens
        System.out.println("  Even numbers: " + evens);
        
        System.out.println("\nMap transformation:");
        
        interface Function<T, R> {
            R apply(T item);
        }
        
        class Mapper {
            public static <T, R> void map(
                    Collection<? extends T> source,  // Producer
                    Collection<? super R> dest,      // Consumer
                    Function<? super T, ? extends R> mapper) {
                for (T item : source) {
                    R result = mapper.apply(item);
                    dest.add(result);
                }
            }
        }
        
        List<String> strings = Arrays.asList("1", "2", "3");
        List<Object> parsed = new ArrayList<>();
        
        Mapper.map(strings, parsed, Integer::parseInt);
        System.out.println("  Parsed: " + parsed);
    }
}
import java.util.*;

public class Pecs {
    static class Stack<E> {
        private List<E> elements = new ArrayList<>();
        
        public void push(E item) {
            elements.add(item);
        }
        
        public E pop() {
            if (elements.isEmpty()) {
                return null;
            }
            return elements.remove(elements.size() - 1);
        }
        
        // Producer - pushes items from src to this stack
        public void pushAll(Iterable<? extends E> src) {
            for (E item : src) {  // Reading from producer
                push(item);
            }
        }
        
        // Consumer - pops items from this stack to dst
        public void popAll(Collection<? super E> dst) {
            while (!elements.isEmpty()) {
                dst.add(pop());  // Writing to consumer
            }
        }
        
        public int size() {
            return elements.size();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("PECS principle:\n");
        
        Stack<Number> numberStack = new Stack<>();
        
        // Producer Extends - can push from Integer list
        List<Integer> ints = Arrays.asList(1, 2, 3);
        numberStack.pushAll(ints);
        
        System.out.println("  Pushed integers, size: " + numberStack.size());
        
        // Consumer Super - can pop to Object list
        List<Object> objects = new ArrayList<>();
        numberStack.popAll(objects);
        
        System.out.println("  Popped to objects: " + objects);
        
        // PECS: Producer Extends, Consumer Super
        // Producer (reading): use <? extends T>
        // Consumer (writing): use <? super T>
        // Maximizes flexibility while maintaining type safety
        
        System.out.println("\nCollection utilities:");
        
        class CollectionUtils {
            // Producer - reading from source
            public static <T> void copy(
                    List<? super T> dest,      // Consumer - writing
                    List<? extends T> src) {   // Producer - reading
                dest.clear();
                for (T item : src) {
                    dest.add(item);
                }
            }
            
            // Producer - reading from multiple sources
            public static <T> void addAll(
                    Collection<? super T> dest,  // Consumer
                    Collection<? extends T>... sources) {  // Producers
                for (Collection<? extends T> src : sources) {
                    for (T item : src) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.5, 2.5);
        List<Number> numberList = new ArrayList<>();
        
        CollectionUtils.addAll(numberList, intList, doubleList);
        System.out.println("  Combined: " + numberList);
        
        System.out.println("\nFrequency counter:");
        
        class Counter {
            public static <T> int frequency(
                    Collection<? extends T> collection,  // Producer
                    T target) {
                int count = 0;
                for (T item : collection) {  // Reading
                    if (item != null && item.equals(target)) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 1, 2);
        int frequencyTarget = ;
        System.out.println("  Frequency of " + frequencyTarget + ": " +
                         Counter.frequency(numbers, frequencyTarget));
        
        System.out.println("\nMax with comparator:");
        
        class MaxFinder {
            public static <T> T max(
                    Collection<? extends T> coll,  // Producer - reading
                    Comparator<? super T> comp) {  // Consumer - comparing
                if (coll.isEmpty()) {
                    return null;
                }
                
                Iterator<? extends T> it = coll.iterator();
                T max = it.next();
                
                while (it.hasNext()) {
                    T item = it.next();
                    if (comp.compare(item, max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        List<String> words = Arrays.asList("apple", "zoo", "banana");
        String maxWord = MaxFinder.max(words, String::compareTo);
        System.out.println("  Max word: " + maxWord);
        
        System.out.println("\nFilter:");
        
        interface Predicate<T> {
            boolean test(T item);
        }
        
        class Filter {
            public static <T> void removeIf(
                    Collection<? extends T> source,   // Producer - reading
                    Collection<? super T> dest,       // Consumer - writing
                    Predicate<? super T> filter) {    // Consumer - testing
                for (T item : source) {
                    if (!filter.test(item)) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Number> evens = new ArrayList<>();
        
        Filter.removeIf(nums, evens, n -> n % 2 != 0);  // Keep evens
        System.out.println("  Even numbers: " + evens);
        
        System.out.println("\nMap transformation:");
        
        interface Function<T, R> {
            R apply(T item);
        }
        
        class Mapper {
            public static <T, R> void map(
                    Collection<? extends T> source,  // Producer
                    Collection<? super R> dest,      // Consumer
                    Function<? super T, ? extends R> mapper) {
                for (T item : source) {
                    R result = mapper.apply(item);
                    dest.add(result);
                }
            }
        }
        
        List<String> strings = Arrays.asList("1", "2", "3");
        List<Object> parsed = new ArrayList<>();
        
        Mapper.map(strings, parsed, Integer::parseInt);
        System.out.println("  Parsed: " + parsed);
    }
}

Read from ? extends. Write to ? super. Copy: src extends, dest super.

PECS Producer Extends, Consumer Super. Guides wildcard choice for read vs write.

Wildcard capture

Convert wildcard to concrete type.

Capture.java
import java.util.*;

public class Capture {
    public static void reverse(List<?> list) {
        reverseHelper(list);  // Capture wildcard
    }
    
    private static <T> void reverseHelper(List<T> list) {
        // Now T is a concrete captured type
        int size = list.size();
        for (int i = 0; i < size / 2; i++) {
            T temp = list.get(i);
            list.set(i, list.get(size - 1 - i));
            list.set(size - 1 - i, temp);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Wildcard capture:\n");
        
        List<Integer> ints = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        System.out.println("  Before: " + ints);
        reverse(ints);
        System.out.println("  After: " + ints);
        
        List<String> strs = new ArrayList<>(Arrays.asList("a", "b", "c"));
        System.out.println("  Before: " + strs);
        reverse(strs);
        System.out.println("  After: " + strs);
        
        // Cannot manipulate elements in List<?> directly
        // Use helper method with <T> to "capture" the type
        // Helper method sees concrete type T
        // Enables operations that need consistent type
        
        System.out.println("\nSwap elements:");
        
        class Swapper {
            public static void swap(List<?> list, int i, int j) {
                swapHelper(list, i, j);
            }
            
            private static <T> void swapHelper(List<T> list, int i, int j) {
                T temp = list.get(i);
                list.set(i, list.get(j));
                list.set(j, temp);
            }
        }
        
        List<Integer> numbers = new ArrayList<>(Arrays.asList(10, 20, 30, 40));
        System.out.println("  Before: " + numbers);
        Swapper.swap(numbers, 0, 3);
        System.out.println("  After: " + numbers);
        
        System.out.println("\nRotate list:");
        
        class Rotator {
            public static void rotate(List<?> list, int distance) {
                rotateHelper(list, distance);
            }
            
            private static <T> void rotateHelper(List<T> list, int distance) {
                int size = list.size();
                if (size == 0) return;
                
                distance = distance % size;
                if (distance < 0) distance += size;
                
                for (int i = 0; i < distance; i++) {
                    T last = list.remove(size - 1);
                    list.add(0, last);
                }
            }
        }
        
        List<String> words = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
        System.out.println("  Before: " + words);
        int rotateDistance = ;
        Rotator.rotate(words, rotateDistance);
        System.out.println("  After: " + words);
        
        System.out.println("\nShuffle:");
        
        class Shuffler {
            public static void shuffle(List<?> list) {
                shuffleHelper(list);
            }
            
            private static <T> void shuffleHelper(List<T> list) {
                Random random = new Random(42);
                for (int i = list.size() - 1; i > 0; i--) {
                    int j = random.nextInt(i + 1);
                    T temp = list.get(i);
                    list.set(i, list.get(j));
                    list.set(j, temp);
                }
            }
        }
        
        List<Integer> deck = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        System.out.println("  Before: " + deck);
        Shuffler.shuffle(deck);
        System.out.println("  After: " + deck);
        
        System.out.println("\nFill with default:");
        
        class Filler {
            public static void fillWithDefault(List<?> list) {
                fillHelper(list);
            }
            
            private static <T> void fillHelper(List<T> list) {
                // Fill with null (default for reference types)
                for (int i = 0; i < list.size(); i++) {
                    list.set(i, null);
                }
            }
        }
        
        List<String> data = new ArrayList<>(Arrays.asList("x", "y", "z"));
        System.out.println("  Before: " + data);
        Filler.fillWithDefault(data);
        System.out.println("  After: " + data);
        
        System.out.println("\nRemove duplicates:");
        
        class Deduplicator {
            public static void removeDuplicates(List<?> list) {
                removeDuplicatesHelper(list);
            }
            
            private static <T> void removeDuplicatesHelper(List<T> list) {
                Set<T> seen = new HashSet<>();
                Iterator<T> it = list.iterator();
                while (it.hasNext()) {
                    T item = it.next();
                    if (!seen.add(item)) {
                        it.remove();
                    }
                }
            }
        }
        
        List<Integer> duplicates = new ArrayList<>(
            Arrays.asList(1, 2, 2, 3, 1, 4, 3, 5));
        System.out.println("  Before: " + duplicates);
        Deduplicator.removeDuplicates(duplicates);
        System.out.println("  After: " + duplicates);
    }
}
import java.util.*;

public class Capture {
    public static void reverse(List<?> list) {
        reverseHelper(list);  // Capture wildcard
    }
    
    private static <T> void reverseHelper(List<T> list) {
        // Now T is a concrete captured type
        int size = list.size();
        for (int i = 0; i < size / 2; i++) {
            T temp = list.get(i);
            list.set(i, list.get(size - 1 - i));
            list.set(size - 1 - i, temp);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Wildcard capture:\n");
        
        List<Integer> ints = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        System.out.println("  Before: " + ints);
        reverse(ints);
        System.out.println("  After: " + ints);
        
        List<String> strs = new ArrayList<>(Arrays.asList("a", "b", "c"));
        System.out.println("  Before: " + strs);
        reverse(strs);
        System.out.println("  After: " + strs);
        
        // Cannot manipulate elements in List<?> directly
        // Use helper method with <T> to "capture" the type
        // Helper method sees concrete type T
        // Enables operations that need consistent type
        
        System.out.println("\nSwap elements:");
        
        class Swapper {
            public static void swap(List<?> list, int i, int j) {
                swapHelper(list, i, j);
            }
            
            private static <T> void swapHelper(List<T> list, int i, int j) {
                T temp = list.get(i);
                list.set(i, list.get(j));
                list.set(j, temp);
            }
        }
        
        List<Integer> numbers = new ArrayList<>(Arrays.asList(10, 20, 30, 40));
        System.out.println("  Before: " + numbers);
        Swapper.swap(numbers, 0, 3);
        System.out.println("  After: " + numbers);
        
        System.out.println("\nRotate list:");
        
        class Rotator {
            public static void rotate(List<?> list, int distance) {
                rotateHelper(list, distance);
            }
            
            private static <T> void rotateHelper(List<T> list, int distance) {
                int size = list.size();
                if (size == 0) return;
                
                distance = distance % size;
                if (distance < 0) distance += size;
                
                for (int i = 0; i < distance; i++) {
                    T last = list.remove(size - 1);
                    list.add(0, last);
                }
            }
        }
        
        List<String> words = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
        System.out.println("  Before: " + words);
        int rotateDistance = ;
        Rotator.rotate(words, rotateDistance);
        System.out.println("  After: " + words);
        
        System.out.println("\nShuffle:");
        
        class Shuffler {
            public static void shuffle(List<?> list) {
                shuffleHelper(list);
            }
            
            private static <T> void shuffleHelper(List<T> list) {
                Random random = new Random(42);
                for (int i = list.size() - 1; i > 0; i--) {
                    int j = random.nextInt(i + 1);
                    T temp = list.get(i);
                    list.set(i, list.get(j));
                    list.set(j, temp);
                }
            }
        }
        
        List<Integer> deck = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        System.out.println("  Before: " + deck);
        Shuffler.shuffle(deck);
        System.out.println("  After: " + deck);
        
        System.out.println("\nFill with default:");
        
        class Filler {
            public static void fillWithDefault(List<?> list) {
                fillHelper(list);
            }
            
            private static <T> void fillHelper(List<T> list) {
                // Fill with null (default for reference types)
                for (int i = 0; i < list.size(); i++) {
                    list.set(i, null);
                }
            }
        }
        
        List<String> data = new ArrayList<>(Arrays.asList("x", "y", "z"));
        System.out.println("  Before: " + data);
        Filler.fillWithDefault(data);
        System.out.println("  After: " + data);
        
        System.out.println("\nRemove duplicates:");
        
        class Deduplicator {
            public static void removeDuplicates(List<?> list) {
                removeDuplicatesHelper(list);
            }
            
            private static <T> void removeDuplicatesHelper(List<T> list) {
                Set<T> seen = new HashSet<>();
                Iterator<T> it = list.iterator();
                while (it.hasNext()) {
                    T item = it.next();
                    if (!seen.add(item)) {
                        it.remove();
                    }
                }
            }
        }
        
        List<Integer> duplicates = new ArrayList<>(
            Arrays.asList(1, 2, 2, 3, 1, 4, 3, 5));
        System.out.println("  Before: " + duplicates);
        Deduplicator.removeDuplicates(duplicates);
        System.out.println("  After: " + duplicates);
    }
}
import java.util.*;

public class Capture {
    public static void reverse(List<?> list) {
        reverseHelper(list);  // Capture wildcard
    }
    
    private static <T> void reverseHelper(List<T> list) {
        // Now T is a concrete captured type
        int size = list.size();
        for (int i = 0; i < size / 2; i++) {
            T temp = list.get(i);
            list.set(i, list.get(size - 1 - i));
            list.set(size - 1 - i, temp);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Wildcard capture:\n");
        
        List<Integer> ints = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        System.out.println("  Before: " + ints);
        reverse(ints);
        System.out.println("  After: " + ints);
        
        List<String> strs = new ArrayList<>(Arrays.asList("a", "b", "c"));
        System.out.println("  Before: " + strs);
        reverse(strs);
        System.out.println("  After: " + strs);
        
        // Cannot manipulate elements in List<?> directly
        // Use helper method with <T> to "capture" the type
        // Helper method sees concrete type T
        // Enables operations that need consistent type
        
        System.out.println("\nSwap elements:");
        
        class Swapper {
            public static void swap(List<?> list, int i, int j) {
                swapHelper(list, i, j);
            }
            
            private static <T> void swapHelper(List<T> list, int i, int j) {
                T temp = list.get(i);
                list.set(i, list.get(j));
                list.set(j, temp);
            }
        }
        
        List<Integer> numbers = new ArrayList<>(Arrays.asList(10, 20, 30, 40));
        System.out.println("  Before: " + numbers);
        Swapper.swap(numbers, 0, 3);
        System.out.println("  After: " + numbers);
        
        System.out.println("\nRotate list:");
        
        class Rotator {
            public static void rotate(List<?> list, int distance) {
                rotateHelper(list, distance);
            }
            
            private static <T> void rotateHelper(List<T> list, int distance) {
                int size = list.size();
                if (size == 0) return;
                
                distance = distance % size;
                if (distance < 0) distance += size;
                
                for (int i = 0; i < distance; i++) {
                    T last = list.remove(size - 1);
                    list.add(0, last);
                }
            }
        }
        
        List<String> words = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
        System.out.println("  Before: " + words);
        int rotateDistance = ;
        Rotator.rotate(words, rotateDistance);
        System.out.println("  After: " + words);
        
        System.out.println("\nShuffle:");
        
        class Shuffler {
            public static void shuffle(List<?> list) {
                shuffleHelper(list);
            }
            
            private static <T> void shuffleHelper(List<T> list) {
                Random random = new Random(42);
                for (int i = list.size() - 1; i > 0; i--) {
                    int j = random.nextInt(i + 1);
                    T temp = list.get(i);
                    list.set(i, list.get(j));
                    list.set(j, temp);
                }
            }
        }
        
        List<Integer> deck = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        System.out.println("  Before: " + deck);
        Shuffler.shuffle(deck);
        System.out.println("  After: " + deck);
        
        System.out.println("\nFill with default:");
        
        class Filler {
            public static void fillWithDefault(List<?> list) {
                fillHelper(list);
            }
            
            private static <T> void fillHelper(List<T> list) {
                // Fill with null (default for reference types)
                for (int i = 0; i < list.size(); i++) {
                    list.set(i, null);
                }
            }
        }
        
        List<String> data = new ArrayList<>(Arrays.asList("x", "y", "z"));
        System.out.println("  Before: " + data);
        Filler.fillWithDefault(data);
        System.out.println("  After: " + data);
        
        System.out.println("\nRemove duplicates:");
        
        class Deduplicator {
            public static void removeDuplicates(List<?> list) {
                removeDuplicatesHelper(list);
            }
            
            private static <T> void removeDuplicatesHelper(List<T> list) {
                Set<T> seen = new HashSet<>();
                Iterator<T> it = list.iterator();
                while (it.hasNext()) {
                    T item = it.next();
                    if (!seen.add(item)) {
                        it.remove();
                    }
                }
            }
        }
        
        List<Integer> duplicates = new ArrayList<>(
            Arrays.asList(1, 2, 2, 3, 1, 4, 3, 5));
        System.out.println("  Before: " + duplicates);
        Deduplicator.removeDuplicates(duplicates);
        System.out.println("  After: " + duplicates);
    }
}

Helper method with type parameter captures the wildcard's actual type.

Exercise: Practical.java

Build a collection copier using PECS