Chuyển tới nội dung chính

Wildcards

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Hiểu khái niệm wildcards (?) và tại sao cần dùng wildcards trong Generics
  • Phân biệt và sử dụng ba loại wildcards: unbounded (<?>), upper bounded (<? extends T>), lower bounded (<? super T>)
  • Nắm vững nguyên tắc PECS (Producer Extends, Consumer Super) để chọn wildcard phù hợp
  • Hiểu wildcard capture và cách giải quyết với helper methods
  • Áp dụng wildcards để viết các utility methods linh hoạt và type-safe

Bài trước: Bounded Type Parameters — Đã học cách giới hạn type parameters với bounds. Bài này sẽ giới thiệu wildcards để làm việc với nhiều generic types khác nhau một cách linh hoạt.

Wildcards là gì?

Wildcard (ký hiệu ?) đại diện cho unknown type trong generics. Wildcards được dùng khi bạn muốn làm việc với nhiều generic types khác nhau.

Định nghĩa

Wildcard ? = "Tôi không quan tâm kiểu cụ thể là gì, miễn là nó thỏa mãn điều kiện nào đó"

Vấn đề dẫn đến Wildcards

public class WildcardProblem {
/**
* Muốn in ra bất kỳ list nào
*/
public static void printList(List<Object> list) {
for (Object obj : list) {
System.out.println(obj);
}
}

public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3);
printList(numbers); // ❌ Compile error!
// List<Integer> KHÔNG phải là subtype của List<Object>!
}
}
Invariance trong Java Generics

List<Integer> KHÔNG PHẢI là subtype của List<Object>, dù Integer là subtype của Object!

Generics trong Java là invariant (bất biến).

Unbounded Wildcard: <?>

Unbounded wildcard <?> chấp nhận bất kỳ type nào.

Cú pháp và sử dụng

public class UnboundedWildcard {
/**
* Chấp nhận List của bất kỳ type nào
*/
public static void printList(List<?> list) {
for (Object obj : list) { // Chỉ có thể đọc như Object
System.out.println(obj);
}
}

/**
* Đếm số phần tử
*/
public static int size(List<?> list) {
return list.size();
}

/**
* Check if empty
*/
public static boolean isEmpty(List<?> list) {
return list.isEmpty();
}

public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<String> words = Arrays.asList("A", "B", "C");
List<Double> decimals = Arrays.asList(1.1, 2.2, 3.3);

// ✅ Tất cả đều OK!
printList(numbers); // 1 2 3
printList(words); // A B C
printList(decimals); // 1.1 2.2 3.3

System.out.println(size(numbers)); // 3
System.out.println(isEmpty(words)); // false
}
}

Giới hạn của Unbounded Wildcard

public class UnboundedLimitations {
public static void demo(List<?> list) {
// ✅ Có thể đọc (nhưng chỉ như Object)
Object obj = list.get(0);

// ✅ Có thể gọi methods không liên quan đến type
int size = list.size();
boolean empty = list.isEmpty();
list.clear();

// ❌ KHÔNG thể thêm (trừ null)
// list.add("Hello"); // Compile error
// list.add(123); // Compile error
list.add(null); // OK - null là giá trị của mọi type
}
}
Khi nào dùng <?>

Dùng unbounded wildcard khi:

  1. Chỉ cần đọc elements như Object
  2. Chỉ dùng methods không phụ thuộc vào type parameter
  3. Muốn code hoạt động với mọi generic type

Ví dụ thực tế: Utility Methods

public class CollectionUtils {
/**
* Check if collection contains null
*/
public static boolean hasNull(Collection<?> collection) {
for (Object obj : collection) {
if (obj == null) {
return true;
}
}
return false;
}

/**
* Clear multiple collections
*/
public static void clearAll(List<?>... collections) {
for (List<?> collection : collections) {
collection.clear();
}
}

/**
* Print all collections
*/
public static void printAll(Collection<?>... collections) {
for (Collection<?> collection : collections) {
System.out.println(collection);
}
}

public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, null, 3));
List<String> words = new ArrayList<>(Arrays.asList("A", "B"));

System.out.println(hasNull(numbers)); // true
System.out.println(hasNull(words)); // false

printAll(numbers, words);
// [1, 2, null, 3]
// [A, B]

clearAll(numbers, words);
System.out.println(numbers.isEmpty()); // true
System.out.println(words.isEmpty()); // true
}
}

Upper Bounded Wildcard: <? extends Type>

Upper bounded wildcard <? extends Type> chấp nhận Type hoặc bất kỳ subtype nào.

Producer - chỉ đọc, không ghi

public class UpperBoundedWildcard {
/**
* Tính tổng của numbers
* Chấp nhận List<Integer>, List<Double>, List<Number>, etc.
*/
public static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number num : numbers) { // Đọc như Number
total += num.doubleValue();
}
return total;
}

/**
* Find max trong list of comparable elements
*/
public static <T extends Comparable<T>> T findMax(
List<? extends T> list) {
if (list.isEmpty()) {
throw new IllegalArgumentException("List is empty");
}

T max = list.get(0);
for (T element : list) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}

public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
List<Number> numbers = Arrays.asList(1, 2.5, 3L);

// ✅ Tất cả đều OK với <? extends Number>
System.out.println(sum(integers)); // 15.0
System.out.println(sum(doubles)); // 6.6
System.out.println(sum(numbers)); // 6.5

System.out.println(findMax(integers)); // 5
System.out.println(findMax(doubles)); // 3.3
}
}

Giới hạn: Không thể ghi (add)

public class UpperBoundedLimitations {
public static void demo(List<? extends Number> numbers) {
// ✅ Có thể đọc như Number
Number num = numbers.get(0);
double d = num.doubleValue();

// ❌ KHÔNG thể thêm (trừ null)
// numbers.add(1); // Compile error
// numbers.add(1.5); // Compile error
// numbers.add(new Integer(5)); // Compile error
numbers.add(null); // OK - null

// Tại sao không thể add?
// List<? extends Number> có thể là List<Integer>
// Nếu cho phép add Double vào, sẽ vi phạm type safety!
}
}
Tại sao không thể add?
List<Integer> integers = new ArrayList<>();
List<? extends Number> numbers = integers; // OK

// Giả sử cho phép:
// numbers.add(1.5); // List<Integer> chứa Double? 💥

// Do đó compiler cấm add vào upper bounded wildcard!

Ví dụ thực tế: Copy từ source

public class WildcardExamples {
/**
* Copy từ source list sang destination list
*/
public static <T> void copy(List<? super T> dest,
List<? extends T> src) {
for (T item : src) {
dest.add(item);
}
}

/**
* Count elements matching predicate
*/
public static <T> long count(Collection<? extends T> collection,
Predicate<T> predicate) {
long count = 0;
for (T element : collection) {
if (predicate.test(element)) {
count++;
}
}
return count;
}

public static void main(String[] args) {
List<Integer> source = Arrays.asList(1, 2, 3, 4, 5);
List<Number> destination = new ArrayList<>();
copy(destination, source);
System.out.println(destination); // [1, 2, 3, 4, 5]

long evenCount = count(source, n -> n % 2 == 0);
System.out.println("Even numbers: " + evenCount); // 2
}
}

Lower Bounded Wildcard: <? super Type>

Lower bounded wildcard <? super Type> chấp nhận Type hoặc bất kỳ supertype nào.

Consumer - chỉ ghi, đọc hạn chế

public class LowerBoundedWildcard {
/**
* Thêm integers vào collection
* Chấp nhận Collection<Integer>, Collection<Number>, Collection<Object>
*/
public static void addIntegers(Collection<? super Integer> collection) {
// ✅ Có thể thêm Integer
collection.add(1);
collection.add(2);
collection.add(3);
// collection.add(1.5); // ❌ Error - Double không phải Integer
}

/**
* Fill collection với values
*/
public static <T> void fill(List<? super T> list, T value, int count) {
for (int i = 0; i < count; i++) {
list.add(value); // OK - add T vào List<? super T>
}
}

public static void main(String[] args) {
// Collection<Integer>
List<Integer> integers = new ArrayList<>();
addIntegers(integers);
System.out.println(integers); // [1, 2, 3]

// Collection<Number> - supertype của Integer
List<Number> numbers = new ArrayList<>();
addIntegers(numbers);
System.out.println(numbers); // [1, 2, 3]

// Collection<Object> - supertype của Integer
List<Object> objects = new ArrayList<>();
addIntegers(objects);
System.out.println(objects); // [1, 2, 3]

// Fill examples
fill(integers, 10, 3);
System.out.println(integers); // [1, 2, 3, 10, 10, 10]
}
}

Giới hạn: Đọc chỉ như Object

public class LowerBoundedLimitations {
public static void demo(List<? super Integer> list) {
// ✅ Có thể thêm Integer (và subtypes nếu có)
list.add(1);
list.add(2);
list.add(Integer.valueOf(3));

// ⚠️ Đọc chỉ được như Object
Object obj = list.get(0); // Chỉ biết nó là Object
// Integer num = list.get(0); // ❌ Compile error

// Phải cast (không type-safe)
Integer num = (Integer) list.get(0); // Phải cast thủ công
}
}
Khi nào dùng <? super T>

Dùng lower bounded wildcard khi:

  1. Muốn thêm elements kiểu T vào collection
  2. Không quan tâm đến việc đọc ra (hoặc chỉ đọc như Object)
  3. Collection đóng vai trò consumer (nhận dữ liệu)

PECS Principle: Producer Extends, Consumer Super

PECS là nguyên tắc quan trọng nhất khi làm việc với wildcards!

PECS Principle
  • Producer Extends: Nếu bạn chỉ lấy dữ liệu ra từ structure → dùng <? extends T>
  • Consumer Super: Nếu bạn chỉ đưa dữ liệu vào structure → dùng <? super T>

Giải thích PECS bằng ví dụ trực quan

public class PECSExample {
/**
* PRODUCER - cung cấp data → extends
*/
public static double sumAll(List<? extends Number> producer) {
double sum = 0;
for (Number num : producer) { // ĐỌC từ producer
sum += num.doubleValue();
}
return sum; // Producer cung cấp data cho ta
}

/**
* CONSUMER - nhận data → super
*/
public static void addNumbers(List<? super Integer> consumer) {
consumer.add(1); // GHI vào consumer
consumer.add(2);
consumer.add(3); // Consumer nhận data từ ta
}

/**
* Copy: src là PRODUCER, dest là CONSUMER
*/
public static <T> void copy(List<? super T> dest, // Consumer
List<? extends T> src) { // Producer
for (T item : src) { // ĐỌC từ producer
dest.add(item); // GHI vào consumer
}
}
}

PECS trong Collections Framework

Nhiều methods trong Java Collections Framework dùng PECS:

// Collections.copy()
public static <T> void copy(
List<? super T> dest, // Consumer Super
List<? extends T> src // Producer Extends
)

// List.addAll()
boolean addAll(Collection<? extends E> c) // Producer Extends

// Collections.max()
public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll) // Producer Extends

PECS in Method Chains

Stream.flatMap()Collections.copy() là ví dụ điển hình:

// Stream.flatMap signature
<R> Stream<R> flatMap(
Function<? super T, ? extends Stream<? extends R>> mapper
)

// Phân tích:
// - Function input: ? super T (Consumer - nhận T)
// - Function output: ? extends Stream<? extends R> (Producer - produce R)
// - Nested wildcard: Stream<? extends R> (Producer elements)

// Ví dụ sử dụng
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("A", "B"),
Arrays.asList("C", "D")
);

List<String> flattened = listOfLists.stream()
.flatMap(list -> list.stream()) // List<String> → Stream<String>
.collect(Collectors.toList());
// Output: [A, B, C, D]

// Collections.copy() signature
public static <T> void copy(
List<? super T> dest, // Consumer: receives T
List<? extends T> src // Producer: provides T
)

// Sử dụng
List<Number> numbers = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0));
List<Integer> integers = Arrays.asList(10, 20, 30);

Collections.copy(numbers, integers); // ✅ OK
// dest: List<? super Integer> = List<Number> ✅
// src: List<? extends Integer> = List<Integer> ✅

Nested Wildcards (Wildcards lồng nhau)

Nested Wildcard Breakdown

Nested wildcards có thể rất phức tạp — phải phân tích từng layer!

// Example: Map<String, ? extends List<? super Integer>>
// Phân tích từng lớp:

Map<String, ? extends List<? super Integer>> complexMap;

// Layer 1: Map<String, ?>
// - Key: String
// - Value: ? extends List<...> → Có thể là bất kỳ List subtype nào

// Layer 2: List<? super Integer>
// - List nhận Integer hoặc supertype
// - Có thể là List<Integer>, List<Number>, List<Object>

// Ví dụ cụ thể
Map<String, List<Number>> map1 = new HashMap<>();
Map<String, List<Object>> map2 = new HashMap<>();
Map<String, ArrayList<Integer>> map3 = new HashMap<>();

complexMap = map1; // ✅ OK - List<Number> extends List, Number super Integer
complexMap = map2; // ✅ OK - List<Object> extends List, Object super Integer
complexMap = map3; // ✅ OK - ArrayList extends List, Integer super Integer

Ví dụ thực tế:

public class NestedWildcards {
// Map students to their course scores
// Map<StudentName, List of scores for each course>

// Method signature với nested wildcards
public static void addScores(
Map<String, ? extends Collection<? super Integer>> scoreMap,
String student,
Integer... scores
) {
Collection<? super Integer> studentScores =
scoreMap.get(student);

if (studentScores != null) {
for (Integer score : scores) {
studentScores.add(score); // OK - add Integer
}
}
}

public static void main(String[] args) {
// Different implementations all work!
Map<String, List<Number>> map1 = new HashMap<>();
map1.put("Alice", new ArrayList<>());

Map<String, Set<Object>> map2 = new HashMap<>();
map2.put("Bob", new HashSet<>());

addScores(map1, "Alice", 90, 85, 92); // ✅ OK
addScores(map2, "Bob", 88, 91); // ✅ OK
}
}

Break down step by step:

Map<String, ? extends Collection<? super Integer>>
^ ^ ^
| | |
| | └─ Inner wildcard: Collection nhận Integer+
| └─ Outer wildcard: Any Collection subtype
└─ Key type: exact String

Wildcards in Return Types (Anti-pattern)

KHÔNG BAO GIỜ return wildcard types — gây khó khăn cho caller!

// ❌ BAD: Wildcard in return type
public List<?> getBadList() {
return Arrays.asList("A", "B", "C");
}

// Caller cannot do anything useful!
List<?> list = getBadList();
// list.add("D"); // ❌ Error - cannot add
// String s = list.get(0); // ❌ Error - returns Object
Object obj = list.get(0); // ⚠️ Only can read as Object

// ✅ GOOD: Specific type or type parameter
public List<String> getGoodList() {
return Arrays.asList("A", "B", "C");
}

List<String> list = getGoodList();
list.add("D"); // ✅ OK
String s = list.get(0); // ✅ OK

// ✅ GOOD: Generic method với type parameter
public <T> List<T> getGenericList(T... elements) {
return Arrays.asList(elements);
}
🔥 Bẫy OCP

OCP Exam: Wildcard trong return type!

// ❌ Useless return type
public List<?> mystery() {
return new ArrayList<String>();
}

List<?> result = mystery();
// result.add(?); // What can I add? NOTHING (except null)!

// Rule: NEVER use wildcards in return types
// Use concrete types or type parameters instead

Unbounded Wildcard Use Cases

Unbounded wildcard <?> khi nào hữu ích?

// 1. Class<?> - Runtime type checking
public void process(Class<?> clazz) {
System.out.println("Processing: " + clazz.getName());
}

process(String.class); // OK
process(Integer.class); // OK
process(List.class); // OK

// 2. instanceof checks
public boolean isList(Object obj) {
return obj instanceof List<?>; // OK
// Cannot: obj instanceof List<String> // Error!
}

// 3. Printing any collection
public void printCollection(Collection<?> coll) {
for (Object elem : coll) {
System.out.println(elem);
}
}

printCollection(Arrays.asList(1, 2, 3));
printCollection(Arrays.asList("A", "B"));
printCollection(new HashSet<>(Arrays.asList(true, false)));

// 4. Generic method không quan tâm type
public int size(Collection<?> coll) {
return coll.size(); // Size không phụ thuộc vào element type
}

public boolean isEmpty(Collection<?> coll) {
return coll.isEmpty();
}

public void clear(Collection<?> coll) {
coll.clear(); // Clear không cần biết element type
}
💡 Cách nhớ

Unbounded <?> = "I don't care about the type"

Dùng khi:

  • Chỉ gọi methods không phụ thuộc type (size, isEmpty, clear)
  • Chỉ đọc elements như Object
  • Runtime type checking (Class<?>, instanceof)

Ví dụ thực tế PECS

public class PECSRealWorld {
/**
* Merge nhiều sources vào một destination
* Sources: Producers (extends)
* Destination: Consumer (super)
*/
@SafeVarargs
public static <T> void mergeAll(
List<? super T> destination,
List<? extends T>... sources) {

for (List<? extends T> source : sources) {
for (T item : source) { // ĐỌC từ source (producer)
destination.add(item); // GHI vào destination (consumer)
}
}
}

/**
* Filter và collect results
*/
public static <T> void filterInto(
Collection<? extends T> source, // Producer
Collection<? super T> destination, // Consumer
Predicate<T> predicate) {

for (T item : source) { // ĐỌC từ source
if (predicate.test(item)) {
destination.add(item); // GHI vào destination
}
}
}

public static void main(String[] args) {
// Merge examples
List<Integer> ints1 = Arrays.asList(1, 2, 3);
List<Integer> ints2 = Arrays.asList(4, 5, 6);
List<Number> allNumbers = new ArrayList<>();

mergeAll(allNumbers, ints1, ints2);
System.out.println(allNumbers); // [1, 2, 3, 4, 5, 6]

// Filter example
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Object> evenNumbers = new ArrayList<>();

filterInto(numbers, evenNumbers, n -> n % 2 == 0);
System.out.println(evenNumbers); // [2, 4, 6]
}
}

So sánh các loại Wildcards

WildcardÝ nghĩaĐọcGhiVai tròVí dụ
<?>Any typeObject❌ (chỉ null)UnknownList<?>
<? extends T>T hoặc subtypeT❌ (chỉ null)ProducerList<? extends Number>
<? super T>T hoặc supertypeObject✅ TConsumerList<? super Integer>
<T>Specific typeT✅ TBothList<String>

Decision Tree

Cần làm gì với collection?

├─ Chỉ ĐỌC ra?
│ └─ Dùng <? extends T> (Producer Extends)

├─ Chỉ GHI vào?
│ └─ Dùng <? super T> (Consumer Super)

├─ Cả ĐỌC và GHI?
│ └─ Dùng <T> (không dùng wildcard)

└─ Không quan tâm type?
└─ Dùng <?> (Unbounded)

Wildcard Capture

Wildcard Capture Mechanism

Wildcard capture là cơ chế compiler "captures" unknown type ? thành synthetic type variable.

Cách Wildcard Capture hoạt động

// Method với wildcard
public static void reverse(List<?> list) {
// Compiler "captures" ? thành synthetic type
// Giống như tạo một type variable T tạm thời
reverseHelper(list);
}

private static <T> void reverseHelper(List<T> list) {
// Bây giờ có type cụ thể T để thao tác
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);
}
}
📖 Theo JLS §5.1.10

"Wildcard capture converts a wildcard parameterized type to a non-wildcard type. This process is invisible to the programmer."

Compiler tự động "capture" ? khi gọi generic method!

Ví dụ capture tự động:

public class AutoCapture {
// Generic method
public static <T> void addToList(List<T> list, T element) {
list.add(element);
}

public static void demo() {
List<?> wildList = new ArrayList<String>();

// ❌ Cannot call directly
// wildList.add("String"); // Error!

// ✅ Compiler captures ? thành T khi gọi generic method
List<String> strings = new ArrayList<>();
addToList(strings, "Hello"); // OK - T captured as String
}
}

Wildcard Capture Helper Pattern

public class WildcardCapture {
/**
* Helper method để capture wildcard
*/
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);
}

/**
* Swap với wildcard - delegate to helper
*/
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j); // Compiler captures ? thành T
}

public static void main(String[] args) {
List<String> words = new ArrayList<>(
Arrays.asList("A", "B", "C")
);

swap(words, 0, 2);
System.out.println(words); // [C, B, A]

List<Integer> numbers = new ArrayList<>(
Arrays.asList(1, 2, 3)
);

swap(numbers, 0, 2);
System.out.println(numbers); // [3, 2, 1]
}
}
Wildcard Capture Helper Pattern

Khi cần làm operations yêu cầu type cụ thể trên wildcard, tạo một private helper method với type parameter <T>.

List<?> vs List<Object> vs raw List

So sánh 3 loại List

Ba cái này HOÀN TOÀN KHÁC NHAU!

FeatureList<?>List<Object>List (raw)
Ý nghĩaList of unknown typeList of ObjectsRaw type (pre-Java 5)
Read asObjectObjectObject
Add elements?❌ No (except null)✅ Yes (Object)⚠️ Yes (unchecked warning)
Type safe?✅ Yes✅ Yes❌ No (warnings)
Accept List<String>?✅ Yes❌ No⚠️ Yes (unsafe)
Accept List<Integer>?✅ Yes❌ No⚠️ Yes (unsafe)
public class ListComparison {
public static void main(String[] args) {
List<String> strings = Arrays.asList("A", "B", "C");

// List<?> - read-only, type-safe
List<?> wildcard = strings; // ✅ OK
Object obj = wildcard.get(0); // ✅ OK - read as Object
// wildcard.add("D"); // ❌ Error - cannot add
wildcard.add(null); // ✅ OK - null only

// List<Object> - requires exact type
// List<Object> objects = strings; // ❌ Error!
// List<String> is not List<Object>

// Raw List - unsafe, backwards compatibility
List raw = strings; // ⚠️ OK but warning
raw.add("D"); // ⚠️ OK but unchecked warning
raw.add(123); // ⚠️ OK but DANGEROUS! Heap pollution!
}

// Method comparison
void methodWildcard(List<?> list) {
// Can accept any List<T>
// Read-only (except null)
}

void methodObject(List<Object> list) {
// Can ONLY accept List<Object>
// Can add Objects
}

void methodRaw(List list) {
// Legacy code - AVOID!
// Unchecked warnings
}
}
🔥 Bẫy OCP

OCP Exam trap: Phân biệt ba loại!

List<String> strings = new ArrayList<>();

// Which works?
List<?> w = strings; // ✅ OK
List<Object> o = strings; // ❌ Error!
List r = strings; // ⚠️ OK but warning

// Which can add?
w.add("X"); // ❌ Error
o.add("X"); // ✅ OK (if o could be assigned)
r.add("X"); // ⚠️ OK but warning

Ví dụ tổng hợp: Generic Collections API

public class GenericCollectionsAPI {
/**
* Reverse any list (unbounded wildcard)
*/
public static void reverse(List<?> list) {
reverseHelper(list);
}

private static <T> void reverseHelper(List<T> list) {
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);
}
}

/**
* Frequency of element (producer extends)
*/
public static <T> int frequency(Collection<? extends T> c, T element) {
int count = 0;
for (T item : c) {
if (item.equals(element)) {
count++;
}
}
return count;
}

/**
* Add all from source to destination (PECS)
*/
public static <T> boolean addAll(
Collection<? super T> dest,
Collection<? extends T> src) {
boolean modified = false;
for (T item : src) {
if (dest.add(item)) {
modified = true;
}
}
return modified;
}

/**
* Index of element (producer extends)
*/
public static <T> int indexOf(List<? extends T> list, T element) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals(element)) {
return i;
}
}
return -1;
}

public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(
Arrays.asList(1, 2, 3, 2, 1)
);

reverse(numbers);
System.out.println(numbers); // [1, 2, 3, 2, 1]

int freq = frequency(numbers, 2);
System.out.println("Frequency of 2: " + freq); // 2

List<Number> allNumbers = new ArrayList<>();
addAll(allNumbers, numbers);
System.out.println(allNumbers); // [1, 2, 3, 2, 1]

int index = indexOf(numbers, 3);
System.out.println("Index of 3: " + index); // 2
}
}

OCP Traps: Wildcard Edge Cases

🔥 Bẫy OCP tổng hợp

Trap 1: List<? extends Number> cannot add anything!

List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(Integer.valueOf(1)); // ❌ Error!
numbers.add(Double.valueOf(1.0)); // ❌ Error!
numbers.add(new Number() {...}); // ❌ Error!
numbers.add(null); // ✅ OK - Only null!

// WHY? Compiler doesn't know exact type
// Could be List<Integer>, List<Double>, List<Long>...
// Cannot guarantee type safety → Reject all adds

Trap 2: List<? super Integer> — Can add Integer, but get() returns Object

List<? super Integer> list = new ArrayList<Number>();
list.add(1); // ✅ OK - Integer
list.add(Integer.valueOf(2)); // ✅ OK

// But reading is limited!
Object obj = list.get(0); // ⚠️ Only Object
// Integer num = list.get(0); // ❌ Error!

// Need cast (not type-safe)
Integer num = (Integer) list.get(0); // ⚠️ Unchecked cast

Trap 3: Assignment trap với super

List<Integer> integers = new ArrayList<>();
List<? super Number> numbers = integers; // ❌ Error!

// WHY? Integer does NOT extend Number's supertype
// Integer extends Number
// But Integer is not a supertype of Number!

// Correct: Number and its supertypes
List<Number> numList = new ArrayList<>();
List<? super Integer> list1 = numList; // ✅ OK - Number super Integer

List<Object> objList = new ArrayList<>();
List<? super Integer> list2 = objList; // ✅ OK - Object super Integer

Mermaid Diagram: PECS Visual Guide

PECS decision flowchart:

Best Practices

Nguyên tắc Wildcards
  1. PECS: Producer Extends, Consumer Super - luôn nhớ!
  2. Unbounded <?>: Khi chỉ dùng Object methods
  3. Avoid wildcards ở return type - gây khó cho caller
  4. Wildcard capture: Dùng helper method khi cần
  5. API flexibility: Wildcards làm API linh hoạt hơn

Bài tập thực hành

Bài 1: Implement Collections methods

// Tìm max element
public static <T extends Comparable<? super T>> T max(
Collection<? extends T> coll) {
// TODO: Implement
}

// Copy collection
public static <T> void copy(
List<? super T> dest,
List<? extends T> src) {
// TODO: Implement
}

Bài 2: PECS trong Stack

Implement các methods cho Stack với PECS:

// Push all from collection
public void pushAll(Iterable<? extends E> src)

// Pop all to collection
public void popAll(Collection<? super E> dest)

Bài 3: Generic Filter

public static <T> List<T> filter(
Collection<? extends T> source,
Predicate<? super T> predicate) {
// TODO: Implement filtering
}

Trong bài tiếp theo, chúng ta sẽ tìm hiểu Type Erasure và những giới hạn của Generics!

Đọc thêm