0

[Java Backend Zero to Hello] [Phase 2] BÀI 2.1: GENERIC

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 2: Java Core nâng cao 📖 Nội dung: BÀI 2.1: GENERIC 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


BÀI 2.1: GENERIC

Mục tiêu

  • Hiểu Generic và lợi ích
  • Khai báo và sử dụng Generic class, method, interface
  • Sử dụng Bounded Type Parameters
  • Hiểu Wildcards

1. GENERIC LÀ GÌ?

Generic cho phép viết class/method có thể làm việc với nhiều kiểu dữ liệu nhưng vẫn đảm bảo type safety tại compile time.

Vấn đề không có Generic

// Trước Java 5 - dùng Object
List list = new ArrayList();
list.add("Hello");
list.add(123);  // Lỗi logic nhưng không báo compile

String s = (String) list.get(1);  // ❌ ClassCastException runtime

Với Generic

List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123);  // ❌ Compile error
String s = list.get(0);  // Không cần cast

2. GENERIC CLASS

public class Box<T> {
    private T content;

    public void set(T content) {
        this.content = content;
    }

    public T get() {
        return content;
    }
}

// Sử dụng
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String s = stringBox.get();

Box<Integer> intBox = new Box<>();
intBox.set(123);
int n = intBox.get();

Quy ước đặt tên Type Parameter

Ký tự Ý nghĩa
T Type
E Element (Collection)
K Key (Map)
V Value (Map)
N Number
S, U, V Type thứ 2, 3, 4

3. GENERIC METHOD

public class Util {
    // Generic method
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    public static <T> T getFirst(List<T> list) {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("List rỗng");
        }
        return list.get(0);
    }

    public static <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) {
        return map.containsKey(key) ? map.get(key) : defaultValue;
    }
}

// Sử dụng
String[] arr = {"A", "B", "C"};
Util.swap(arr, 0, 2);  // [C, B, A]

List<Integer> list = Arrays.asList(1, 2, 3);
Integer first = Util.getFirst(list);  // 1

4. BOUNDED TYPE PARAMETERS

Giới hạn kiểu dữ liệu được phép.

4.1 Upper Bounded - extends

// Chỉ chấp nhận Number và các class con
public class NumberBox<T extends Number> {
    private T number;

    public double doubleValue() {
        return number.doubleValue();
    }
}

NumberBox<Integer> intBox = new NumberBox<>();   // OK
NumberBox<Double> doubleBox = new NumberBox<>();  // OK
// NumberBox<String> strBox = new NumberBox<>();  // ❌ Compile error

4.2 Multiple Bounds

public class MyClass<T extends Number & Comparable<T>> {
    // T phải là Number và implement Comparable
}

MyClass<Integer> obj = new MyClass<>();  // OK
// MyClass<String> obj2 = new MyClass<>();  // ❌

5. WILDCARDS

5.1 Unbounded Wildcard - <?>

public void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

printList(Arrays.asList(1, 2, 3));      // OK
printList(Arrays.asList("A", "B"));     // OK

5.2 Upper Bounded Wildcard - <? extends T>

// Chấp nhận List<Integer>, List<Double>, List<Number>
public double sum(List<? extends Number> list) {
    double total = 0;
    for (Number n : list) {
        total += n.doubleValue();
    }
    return total;
}

sum(Arrays.asList(1, 2, 3));           // OK
sum(Arrays.asList(1.5, 2.5));          // OK
// sum(Arrays.asList("A", "B"));       // ❌

5.3 Lower Bounded Wildcard - <? super T>

// Chấp nhận List<Integer>, List<Number>, List<Object>
public void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 5; i++) {
        list.add(i);
    }
}

List<Integer> intList = new ArrayList<>();
addNumbers(intList);  // OK

List<Number> numList = new ArrayList<>();
addNumbers(numList);  // OK

5.4 PECS Rule

  • Producer Extends: Dùng ? extends T khi chỉ đọc (lấy ra)
  • Consumer Super: Dùng ? super T khi chỉ ghi (thêm vào)
// Producer - chỉ đọc
public void copy(List<? extends Number> source, List<? super Number> dest) {
    for (Number n : source) {
        dest.add(n);
    }
}

6. TYPE ERASURE

Java Generic chỉ tồn tại ở compile time, bị xóa ở runtime (type erasure).

// Source code
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();

// Sau khi compile (bytecode)
List stringList = new ArrayList();
List intList = new ArrayList();
// Cả hai đều là List<Object> ở runtime

Hệ quả

  • Không thể new T() trong generic class
  • Không thể tạo mảng generic: new T[10] ❌
  • Không thể dùng instanceof với generic type
public class Box<T> {
    private T value;

    public Box() {
        // value = new T();  // ❌ Compile error
    }

    public boolean isInstance(Object obj) {
        // return obj instanceof T;  // ❌ Compile error
        return obj instanceof Box;  // OK
    }
}

7. GENERIC INTERFACE

public interface Repository<T, ID> {
    T findById(ID id);
    List<T> findAll();
    T save(T entity);
    void deleteById(ID id);
}

public class UserRepository implements Repository<User, Long> {
    @Override
    public User findById(Long id) { /* ... */ return null; }

    @Override
    public List<User> findAll() { /* ... */ return null; }

    @Override
    public User save(User entity) { /* ... */ return null; }

    @Override
    public void deleteById(Long id) { /* ... */ }
}

8. BÀI TẬP THỰC HÀNH

Bài 1: Generic Stack

public class Stack<T> {
    private List<T> elements = new ArrayList<>();

    public void push(T item) {
        elements.add(item);
    }

    public T pop() {
        if (isEmpty()) throw new EmptyStackException();
        return elements.remove(elements.size() - 1);
    }

    public T peek() {
        if (isEmpty()) throw new EmptyStackException();
        return elements.get(elements.size() - 1);
    }

    public boolean isEmpty() {
        return elements.isEmpty();
    }

    public int size() {
        return elements.size();
    }
}

Bài 2: Generic Pair

public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }

    public static <K, V> Pair<K, V> of(K key, V value) {
        return new Pair<>(key, value);
    }
}

Pair<String, Integer> pair = Pair.of("Age", 25);

Bài 3: Generic Utility

public class CollectionUtil {
    public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T item : list) {
            if (predicate.test(item)) {
                result.add(item);
            }
        }
        return result;
    }

    public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
        List<R> result = new ArrayList<>();
        for (T item : list) {
            result.add(mapper.apply(item));
        }
        return result;
    }
}

9. TÓM TẮT

Khái niệm Cú pháp Mục đích
Generic class class Box<T> Class làm việc với nhiều kiểu
Generic method <T> void method(T t) Method linh hoạt
Bounded <T extends Number> Giới hạn kiểu
Wildcard <?> Kiểu không xác định
Upper bound <? extends T> Producer
Lower bound <? super T> Consumer
Type erasure - Generic chỉ ở compile time

Bài tiếp theo: 2.2 Lambda Expression


🧭 Điều hướng bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí