Generics
Wildcards
Flexible Type Arguments
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
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
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