Bounded Type Parameters
Sau bài này, bạn sẽ:
- Hiểu vấn đề của unbounded type parameters và giải pháp với bounded types
- Sử dụng upper bounded types (
<T extends Type>) để giới hạn type parameters - Áp dụng multiple bounds với class và interfaces
- Hiểu và sử dụng recursive type bounds như
\<T extends Comparable\<T\>\> - Implement các generic methods với bounds phù hợp cho các operations cần thiết
Bài trước: Generic Classes và Methods — Đã học cách tạo generic classes và methods. Bài này sẽ hướng dẫn cách giới hạn type parameters để có thể gọi specific methods.
Vấn đề với Unbounded Type Parameters
Khi sử dụng <T> không có bound, bạn chỉ có thể gọi methods của Object:
public class UnboundedProblem {
/**
* Không thể gọi compareTo() vì T không chắc có method này
*/
public static <T> T findMax(T a, T b) {
// ❌ Compile error: cannot find symbol method compareTo
// return a.compareTo(b) > 0 ? a : b;
// Chỉ có thể gọi methods của Object
a.toString();
a.equals(b);
a.hashCode();
return a; // Không biết cái nào lớn hơn!
}
}
Với <T> thuần, compiler chỉ biết T là Object, không thể gọi các methods đặc thù!
Upper Bounded Type Parameters
Upper bound giới hạn type parameter phải là subclass của một class hoặc implement một interface.
Cú pháp
<T extends UpperBound>
Dù là class hay interface, đều dùng từ khóa extends (không dùng implements)!
Ví dụ: Bounded với Number
/**
* T phải là Number hoặc subclass của Number
*/
public class NumberBox<T extends Number> {
private T value;
public NumberBox(T value) {
this.value = value;
}
public T getValue() {
return value;
}
/**
* Có thể gọi methods của Number
*/
public double getDoubleValue() {
return value.doubleValue(); // OK vì T extends Number
}
public int getIntValue() {
return value.intValue(); // OK
}
/**
* Tính bình phương
*/
public double getSquare() {
double d = value.doubleValue();
return d * d;
}
}
Sử dụng NumberBox
public class NumberBoxDemo {
public static void main(String[] args) {
// ✅ OK - Integer extends Number
NumberBox<Integer> intBox = new NumberBox<>(42);
System.out.println(intBox.getDoubleValue()); // 42.0
System.out.println(intBox.getSquare()); // 1764.0
// ✅ OK - Double extends Number
NumberBox<Double> doubleBox = new NumberBox<>(3.14);
System.out.println(doubleBox.getIntValue()); // 3
// ✅ OK - Long extends Number
NumberBox<Long> longBox = new NumberBox<>(1000000L);
// ❌ Compile error - String không extends Number
// NumberBox<String> stringBox = new NumberBox<>("Hello");
}
}
Generic Method với Upper Bound
public class BoundedMethods {
/**
* Tính tổng của array numbers
*/
public static <T extends Number> double sum(T[] numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue(); // OK vì T extends Number
}
return total;
}
/**
* Tìm max trong array numbers
*/
public static <T extends Number> T findMax(T[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
T max = numbers[0];
for (T num : numbers) {
if (num.doubleValue() > max.doubleValue()) {
max = num;
}
}
return max;
}
public static void main(String[] args) {
Integer[] integers = {5, 2, 8, 1, 9};
System.out.println("Sum: " + sum(integers)); // 25.0
System.out.println("Max: " + findMax(integers)); // 9
Double[] doubles = {3.14, 2.71, 1.41, 1.73};
System.out.println("Sum: " + sum(doubles)); // 9.0
System.out.println("Max: " + findMax(doubles)); // 3.14
// ❌ Compile error
// String[] strings = {"a", "b", "c"};
// sum(strings); // String không extends Number
}
}
Bounded với Interface: Comparable
Ví dụ phổ biến nhất là bound với Comparable:
/**
* T phải implement Comparable<T>
*/
public class ComparableUtils {
/**
* Tìm min element
*/
public static <T extends Comparable<T>> T findMin(T[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array is empty");
}
T min = array[0];
for (T element : array) {
if (element.compareTo(min) < 0) {
min = element;
}
}
return min;
}
/**
* Tìm max element
*/
public static <T extends Comparable<T>> T findMax(T[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array is empty");
}
T max = array[0];
for (T element : array) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
/**
* Sắp xếp array (bubble sort đơn giản)
*/
public static <T extends Comparable<T>> void sort(T[] array) {
int n = array.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (array[j].compareTo(array[j + 1]) > 0) {
T temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
}
Sử dụng với Comparable
public class ComparableDemo {
public static void main(String[] args) {
// String implements Comparable<String>
String[] names = {"Charlie", "Alice", "Bob"};
System.out.println("Min: " + ComparableUtils.findMin(names)); // Alice
System.out.println("Max: " + ComparableUtils.findMax(names)); // Charlie
ComparableUtils.sort(names);
System.out.println("Sorted: " + Arrays.toString(names));
// [Alice, Bob, Charlie]
// Integer implements Comparable<Integer>
Integer[] numbers = {5, 2, 8, 1, 9};
System.out.println("Min: " + ComparableUtils.findMin(numbers)); // 1
System.out.println("Max: " + ComparableUtils.findMax(numbers)); // 9
// Custom class implements Comparable
Person[] people = {
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 20)
};
Person youngest = ComparableUtils.findMin(people);
System.out.println("Youngest: " + youngest); // Charlie, 20
}
}
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + ", " + age;
}
}
Multiple Bounds (Đa Bounds)
Biểu đồ Multiple Bounds Structure
Type parameter có thể có nhiều bounds (class và/hoặc interfaces):
Cú pháp
<T extends ClassBound & InterfaceBound1 & InterfaceBound2>
- Class phải đứng đầu (nếu có)
- Interfaces theo sau, ngăn cách bởi
& - Chỉ được có tối đa 1 class (vì Java không có multiple inheritance)
- Có thể có nhiều interfaces
// ✅ OK - class trước, interfaces sau
<T extends Number & Comparable<T> & Serializable>
// ✅ OK - chỉ có interfaces
<T extends Comparable<T> & Serializable & Cloneable>
// ❌ Error - class không ở đầu
<T extends Comparable<T> & Number>
// ❌ Error - có 2 classes
<T extends Number & Integer>
Tại sao class phải đầu tiên? (WHY?)
"If a bound has the form T extends C & I1 & I2, then the erasure of T is the erasure of the leftmost bound (C)."
Compiler cần biết bound đầu tiên để type erasure! Class bound phải đầu để JVM biết superclass nào.
// Multiple bounds ordering
<T extends Number & Comparable<T>>
// Erasure: T → Number (first bound)
<T extends Comparable<T> & Number> // ❌ Error!
// Nếu cho phép: Erasure T → Comparable (interface)
// JVM constraint: Erasure phải là class, không thể là interface!
Ví dụ Multiple Bounds
import java.io.Serializable;
/**
* T phải extends Number VÀ implement Comparable
*/
public class AdvancedCalculator {
/**
* Tính tổng và trả về kết quả có thể serialize
*/
public static <T extends Number & Serializable> double sum(List<T> numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue(); // OK - T extends Number
}
// Có thể serialize T nếu cần - T implements Serializable
return total;
}
/**
* Find max với comparable và serializable
*/
public static <T extends Number & Comparable<T>> T findMax(List<T> numbers) {
if (numbers.isEmpty()) {
throw new IllegalArgumentException("List is empty");
}
T max = numbers.get(0);
for (T num : numbers) {
if (num.compareTo(max) > 0) { // OK - T extends Comparable<T>
max = num;
}
}
return max;
}
}
// Sử dụng
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
// Integer extends Number & implements Comparable & Serializable
double total = AdvancedCalculator.sum(numbers); // 25.0
Integer max = AdvancedCalculator.findMax(numbers); // 9
Ví dụ thực tế: Generic Sorting với Multiple Bounds
/**
* Sortable collection với multiple bounds
*/
public class SortableList<T extends Comparable<T> & Cloneable> {
private List<T> items;
public SortableList() {
this.items = new ArrayList<>();
}
public void add(T item) {
items.add(item);
}
/**
* Sort items - cần Comparable
*/
public void sort() {
Collections.sort(items);
}
/**
* Clone all items - cần Cloneable
*/
public List<T> cloneItems() {
List<T> clones = new ArrayList<>();
for (T item : items) {
// T implements Cloneable
clones.add(item);
}
return clones;
}
/**
* Get sorted copy
*/
public List<T> getSortedCopy() {
List<T> copy = cloneItems();
Collections.sort(copy);
return copy;
}
@Override
public String toString() {
return items.toString();
}
}
Recursive Type Bounds
Recursive Type Bound Visualization
Recursive type bound là pattern phổ biến: <T extends Comparable<T>>
Tại sao Recursive?
// T phải comparable với chính nó
<T extends Comparable<T>>
Điều này đảm bảo:
Tcó methodcompareTo(T other)- Có thể so sánh
TvớiTkhác
Ví dụ: Generic Min/Max
public class RecursiveBoundExample {
/**
* Find min với recursive bound
*/
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) <= 0 ? a : b;
}
/**
* Find max với recursive bound
*/
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
/**
* Find min trong varargs
*/
@SafeVarargs
public static <T extends Comparable<T>> T min(T... elements) {
if (elements.length == 0) {
throw new IllegalArgumentException("No elements");
}
T min = elements[0];
for (int i = 1; i < elements.length; i++) {
if (elements[i].compareTo(min) < 0) {
min = elements[i];
}
}
return min;
}
public static void main(String[] args) {
// Integer implements Comparable<Integer>
System.out.println(min(5, 3)); // 3
System.out.println(max(5, 3)); // 5
System.out.println(min(5, 2, 8, 1, 9)); // 1
// String implements Comparable<String>
System.out.println(min("apple", "banana")); // apple
System.out.println(min("dog", "cat", "bird")); // bird
// Custom class với Comparable
Student s1 = new Student("Alice", 85);
Student s2 = new Student("Bob", 92);
System.out.println(max(s1, s2)); // Bob (92)
}
}
class Student implements Comparable<Student> {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.score, other.score);
}
@Override
public String toString() {
return name + " (" + score + ")";
}
}
Tại sao không dùng <T extends Comparable>?
// ❌ BAD - raw type Comparable
public static <T extends Comparable> T min(T a, T b) {
return a.compareTo(b) <= 0 ? a : b;
// compareTo() nhận Object, không type-safe!
}
// ✅ GOOD - recursive bound
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) <= 0 ? a : b;
// compareTo() nhận T, type-safe!
}
Erasure of Bounded Types
Bounded Type Erasure Flow
Type erasure với bounded types thay T bằng first bound, không phải Object!
// Source code
public class NumberBox<T extends Number> {
private T value;
public T getValue() {
return value;
}
public double doubleValue() {
return value.doubleValue(); // OK - T extends Number
}
}
// After type erasure → bytecode
public class NumberBox {
private Number value; // T → Number (first bound)
public Number getValue() {
return value;
}
public double doubleValue() {
return value.doubleValue(); // Method available!
}
}
Performance implication:
// <T> erases to Object → method call needs cast
public <T> T getValue(Box<T> box) {
return box.get(); // Bytecode: (Object) box.get()
}
// <T extends Number> erases to Number → better performance
public <T extends Number> double getDouble(NumberBox<T> box) {
return box.getValue().doubleValue();
// Bytecode: ((Number) box.getValue()).doubleValue()
// No cast needed if already Number!
}
Bounded types → Better erasure → Better performance
<T>→Object→ Nhiều casting<T extends Number>→Number→ Ít casting hơn<T extends Number & Comparable<T>>→Number(first bound)
Recursive Type Bounds Deep Dive
Self-Bounded Types: Enum Pattern
Enum pattern là ví dụ kinh điển về self-bounded types:
// Enum trong JDK định nghĩa như sau:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
// E phải là subtype của Enum<E>
// "I am a kind of Enum that is specifically me"
}
Phân tích từng bước:
// Bước 1: E extends Enum<E>
// Có nghĩa: E là subclass của Enum, và Enum được parameterized bởi chính E
// Ví dụ cụ thể:
enum Color extends Enum<Color> { // E = Color
RED, GREEN, BLUE
}
// Đọc: "Color extends Enum<Color>"
// → Color là một Enum được parameterized bởi Color
Tại sao cần self-bounded?
public abstract class Enum<E extends Enum<E>> implements Comparable<E> {
// compareTo() nhận E, không phải Enum!
public final int compareTo(E other) {
// Type-safe: chỉ so sánh cùng enum type
return this.ordinal - other.ordinal;
}
}
// Nếu không có self-bound:
// public abstract class Enum<E> implements Comparable<E>
// Thì có thể: Color.RED.compareTo(Day.MONDAY) → Sai logic!
// Với self-bound:
// Color.RED chỉ so sánh với Color khác
// Color.RED.compareTo(Day.MONDAY) → Compile error! ✅
Builder Pattern với Self-Bounded Types
// Generic Builder với self-bounded type
public abstract class Builder<T, B extends Builder<T, B>> {
protected T object;
// Subclass override để return chính nó
protected abstract B self();
public B setAttribute(String attr) {
// Set attribute...
return self();
}
public abstract T build();
}
// Concrete builder
public class PersonBuilder extends Builder<Person, PersonBuilder> {
@Override
protected PersonBuilder self() {
return this;
}
public PersonBuilder setName(String name) {
// Set name...
return self(); // Return PersonBuilder, not Builder
}
@Override
public Person build() {
return new Person(object);
}
}
// Method chaining works perfectly!
Person p = new PersonBuilder()
.setName("Alice") // Returns PersonBuilder
.setAttribute("age") // Returns PersonBuilder
.build(); // Returns Person
Edge Case: <T extends Comparable<? super T>>
Tại sao cần ? super T? Cho phép so sánh với superclass!
// Simple version
<T extends Comparable<T>> T max(List<T> list)
// Problem: Không hoạt động với subclass không implement Comparable
class Animal implements Comparable<Animal> {
public int compareTo(Animal other) { ... }
}
class Dog extends Animal {
// Dog không override compareTo
}
List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
Animal max = max(dogs); // ❌ Error!
// Dog không extends Comparable<Dog>
// Dog chỉ extends Comparable<Animal> (từ superclass)
// Solution: <T extends Comparable<? super T>>
<T extends Comparable<? super T>> T max(List<T> list)
// Giờ OK: T = Dog
// Comparable<? super Dog> = Comparable<Animal> ✅
// Dog có thể so sánh với Animal (superclass)
public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll) {
// Signature phức tạp nhất trong JDK!
// Cho phép flexibility tối đa
}
Bridge Methods và Bounded Types
Bridge methods được compiler tạo ra khi subclass override methods với bounded types:
// Generic class
class Box<T extends Number> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// Subclass với concrete type
class IntBox extends Box<Integer> {
@Override
public void set(Integer value) {
super.set(value);
}
@Override
public Integer get() {
return super.get();
}
}
Sau type erasure:
// Box sau erasure
class Box {
private Number value;
public void set(Number value) { // T → Number (bound)
this.value = value;
}
public Number get() {
return value;
}
}
// IntBox sau erasure - CÓ BRIDGE METHODS!
class IntBox extends Box {
// Your method
public void set(Integer value) {
super.set(value);
}
// Bridge method (compiler-generated)
public void set(Number value) { // Override parent's set(Number)
this.set((Integer) value); // Cast và delegate
}
// Your method
public Integer get() {
return super.get();
}
// Bridge method (compiler-generated)
public Number get() { // Override parent's get()
return this.get(); // Delegate, return type covariant
}
}
Xem bridge methods bằng reflection:
import java.lang.reflect.Method;
public class BridgeMethodDemo {
public static void main(String[] args) {
for (Method m : IntBox.class.getDeclaredMethods()) {
System.out.println(m.getName() + " - Bridge: " + m.isBridge());
}
// Output:
// set - Bridge: false (your method)
// set - Bridge: true (synthetic bridge)
// get - Bridge: false (your method)
// get - Bridge: true (synthetic bridge)
}
}
Multiple bounds với conflicting methods:
interface A {
void doSomething();
}
interface B {
void doSomething();
}
// ✅ OK - cùng method signature
class C<T extends A & B> {
void test(T obj) {
obj.doSomething(); // OK - chỉ một method
}
}
// ❌ Error nếu conflict
interface A {
String getValue();
}
interface B {
Integer getValue(); // Different return type!
}
class C<T extends A & B> { // ❌ Compile error!
// Cannot combine A and B - incompatible return types
}
Bound type trong static context:
class Box<T extends Number> {
// ❌ Error: Cannot use T in static context
// private static T defaultValue;
// ❌ Error
// public static T getDefault() { return null; }
// ✅ OK: Static method với own type parameter
public static <E extends Number> E create(E value) {
return value;
}
}
Ví dụ thực tế: Generic Range Class
/**
* Range của một kiểu comparable
*/
public class Range<T extends Comparable<T>> {
private T min;
private T max;
public Range(T min, T max) {
if (min.compareTo(max) > 0) {
throw new IllegalArgumentException("min > max");
}
this.min = min;
this.max = max;
}
/**
* Check nếu value nằm trong range
*/
public boolean contains(T value) {
return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
}
/**
* Check nếu range này overlap với range khác
*/
public boolean overlaps(Range<T> other) {
return this.min.compareTo(other.max) <= 0 &&
this.max.compareTo(other.min) >= 0;
}
/**
* Get intersection với range khác
*/
public Range<T> intersection(Range<T> other) {
if (!overlaps(other)) {
throw new IllegalArgumentException("Ranges don't overlap");
}
T newMin = this.min.compareTo(other.min) >= 0 ? this.min : other.min;
T newMax = this.max.compareTo(other.max) <= 0 ? this.max : other.max;
return new Range<>(newMin, newMax);
}
@Override
public String toString() {
return "[" + min + ", " + max + "]";
}
}
Sử dụng Range
public class RangeDemo {
public static void main(String[] args) {
// Integer range
Range<Integer> ageRange = new Range<>(18, 65);
System.out.println(ageRange.contains(25)); // true
System.out.println(ageRange.contains(70)); // false
Range<Integer> youngRange = new Range<>(15, 30);
System.out.println(ageRange.overlaps(youngRange)); // true
Range<Integer> overlap = ageRange.intersection(youngRange);
System.out.println(overlap); // [18, 30]
// String range (alphabetical)
Range<String> letterRange = new Range<>("A", "M");
System.out.println(letterRange.contains("F")); // true
System.out.println(letterRange.contains("Z")); // false
// LocalDate range
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 12, 31);
Range<LocalDate> yearRange = new Range<>(start, end);
LocalDate today = LocalDate.now();
System.out.println(yearRange.contains(today));
}
}
So sánh các loại Bounds
| Loại Bound | Cú pháp | Khi nào dùng | Ví dụ |
|---|---|---|---|
| Unbounded | <T> | Chỉ dùng Object methods | Box<T> |
| Upper Bound | <T extends Type> | Cần methods của Type | <T extends Number> |
| Multiple Bounds | <T extends A & B & C> | Cần methods của nhiều types | <T extends Number & Comparable<T>> |
| Recursive Bound | <T extends Comparable<T>> | Self-comparable | Min/Max methods |
Mermaid Diagram: Bounded Type Erasure Flow
Giải thích flow:
- Bounded type
<T extends Number>trong source code - Compiler kiểm tra type constraints
- Type erasure thay
TbằngNumber(first bound) - Casts được chèn tự động nơi cần
- Bridge methods tạo ra để maintain polymorphism
- Bytecode chỉ chứa
Number, không cóT
Best Practices
- Bound hẹp nhất có thể: Chỉ bound với những gì thực sự cần
- Recursive bounds cho comparable:
<T extends Comparable<T>> - Multiple bounds khi cần nhiều capabilities
- Class trước, interfaces sau trong multiple bounds
- Avoid raw types: Luôn dùng
Comparable<T>thay vìComparable
Bài tập thực hành
Bài 1: Generic Statistics
Tạo class Statistics<T> để tính toán thống kê:
public class Statistics<T extends Number> {
// TODO: Implement
// - average()
// - min()
// - max()
// - sum()
}
Bài 2: Generic Pair với Comparable
Tạo ComparablePair<T> mà chính nó comparable:
public class ComparablePair<T extends Comparable<T>>
implements Comparable<ComparablePair<T>> {
// TODO: So sánh dựa trên first, nếu bằng thì so sánh second
}
Bài 3: Generic Sorter
Implement generic sorter với multiple sorting strategies:
public class Sorter {
public static <T extends Comparable<T>> void sort(List<T> list) {
// TODO: Implement sorting
}
public static <T extends Comparable<T>> boolean isSorted(List<T> list) {
// TODO: Check if sorted
}
}
Trong bài tiếp theo, chúng ta sẽ tìm hiểu về Wildcards - một công cụ mạnh mẽ khác của Generics!