Generic Programming trong Java
1. Khái Niệm Cơ Bản
1.1 Generic là gì?
- Định nghĩa:
- Cho phép viết code có thể tái sử dụng với nhiều kiểu dữ liệu
- Kiểm tra kiểu tại thời điểm biên dịch
-
Loại bỏ việc ép kiểu thủ công
-
Lợi ích:
- Type safety: Phát hiện lỗi tại compile-time
- Code reusability: Tái sử dụng code với nhiều kiểu
- Performance: Không cần type casting
- Clean code: Code rõ ràng, dễ đọc
1.2 Cú Pháp Cơ Bản
// 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 Generic");
String content = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(123);
int number = intBox.get();
2. Generic Methods và Wildcards
2.1 Generic Methods
- Đặc điểm:
- Định nghĩa kiểu generic cho method
- Độc lập với class generic
- Type inference tự động
public class GenericMethods {
// Generic method
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// Generic method với nhiều type parameters
public <T, U> Pair<T, U> makePair(T first, U second) {
return new Pair<>(first, second);
}
// Generic method với type bounds
public <T extends Number> double sum(List<T> numbers) {
double sum = 0.0;
for (T number : numbers) {
sum += number.doubleValue();
}
return sum;
}
}
// Sử dụng
GenericMethods gm = new GenericMethods();
String[] strings = {"Hello", "World"};
gm.printArray(strings);
Pair<String, Integer> pair = gm.makePair("Key", 123);
2.2 Wildcards
- Upper Bounded Wildcards:
- Sử dụng
<? extends Type> - Cho phép Type và các lớp con của nó
-
Read-only access
-
Lower Bounded Wildcards:
- Sử dụng
<? super Type> - Cho phép Type và các lớp cha của nó
-
Write access
-
Unbounded Wildcards:
- Sử dụng
<?> - Cho phép mọi kiểu
- Sử dụng khi không quan tâm đến kiểu
public class WildcardExample {
// Upper bounded wildcard
public double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
// Lower bounded wildcard
public void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
// Unbounded wildcard
public void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
}
3. Type Bounds và Constraints
3.1 Type Bounds
- Single Bound:
- Giới hạn kiểu generic bằng một interface/class
- Sử dụng từ khóa extends
-
Đảm bảo kiểu có các phương thức cần thiết
-
Multiple Bounds:
- Giới hạn kiểu bằng nhiều interface
- Chỉ được extends một class
- Class phải đứng đầu danh sách bounds
// Single bound
public class NumberBox<T extends Number> {
private T number;
public double getValue() {
return number.doubleValue();
}
}
// Multiple bounds
public class DataProcessor<T extends Comparable<T> & Serializable> {
public void process(T data) {
// Process data
}
}
// Sử dụng với interface tự định nghĩa
interface Processable {
void process();
}
interface Storable {
void save();
}
public class ProcessableData<T extends Processable & Storable> {
private T data;
public void executeAll() {
data.process();
data.save();
}
}
3.2 Type Erasure
- Cơ chế:
- Chuyển đổi code generic thành non-generic
- Thay thế type parameters bằng bounds
- Thêm type casting khi cần thiết
// Trước type erasure
public class Box<T> {
private T content;
public T get() { return content; }
public void set(T content) { this.content = content; }
}
// Sau type erasure
public class Box {
private Object content;
public Object get() { return content; }
public void set(Object content) { this.content = content; }
}
4. Generic Collections
4.1 Collection Classes
- List Generic:
- ArrayList
- LinkedList
-
Vector
-
Set Generic:
- HashSet
- TreeSet
-
LinkedHashSet
-
Map Generic:
- HashMap
- TreeMap
- LinkedHashMap
public class GenericCollections {
public void demonstrateCollections() {
// List
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
String first = stringList.get(0);
// Set
Set<Integer> numberSet = new HashSet<>();
numberSet.add(1);
numberSet.add(2);
// Map
Map<String, Integer> scores = new HashMap<>();
scores.put("John", 95);
scores.put("Alice", 98);
// Nested generics
Map<String, List<Integer>> studentScores = new HashMap<>();
studentScores.put("John", Arrays.asList(95, 87, 98));
}
}
4.2 Custom Generic Collections
// Generic Stack
public class Stack<T> {
private List<T> items = new ArrayList<>();
public void push(T item) {
items.add(item);
}
public T pop() {
if (items.isEmpty()) {
throw new EmptyStackException();
}
return items.remove(items.size() - 1);
}
public boolean isEmpty() {
return items.isEmpty();
}
}
// 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 void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
}
5. Best Practices và Design Patterns
5.1 Best Practices
- Nguyên tắc thiết kế:
- Sử dụng generics cho type safety
- Ưu tiên bounded wildcards
- Tránh raw types
- Đặt tên type parameters có ý nghĩa
public class GenericBestPractices {
// BAD: Raw type
List list = new ArrayList();
// GOOD: Generic type
List<String> stringList = new ArrayList<>();
// BAD: Object type
List<Object> objectList = new ArrayList<>();
// GOOD: Wildcard
List<?> unknownList = new ArrayList<>();
// GOOD: Bounded wildcard
public void processNumbers(List<? extends Number> numbers) {
// Process numbers
}
}
5.2 Common Design Patterns với Generics
// Generic Singleton
public class Singleton<T> {
private static Singleton<?> instance;
private final T value;
private Singleton(T value) {
this.value = value;
}
public static <T> Singleton<T> getInstance(T value) {
if (instance == null) {
instance = new Singleton<>(value);
}
return (Singleton<T>) instance;
}
}
// Generic Factory
public interface Product { }
public class ConcreteProduct implements Product { }
public class GenericFactory<T extends Product> {
public T createProduct(Class<T> productClass)
throws InstantiationException,
IllegalAccessException {
return productClass.newInstance();
}
}
// Generic Builder
public class GenericBuilder<T> {
private final T instance;
private final Map<String, Consumer<Object>> setters = new HashMap<>();
public GenericBuilder(Class<T> clazz)
throws InstantiationException,
IllegalAccessException {
this.instance = clazz.newInstance();
}
public <U> GenericBuilder<T> with(String property, U value) {
Consumer<Object> setter = setters.get(property);
if (setter != null) {
setter.accept(value);
}
return this;
}
public T build() {
return instance;
}
}