Nghe thì liên tưởng nó là cái gì đó cao siêu khó hiểu khó làm, nhưng thực ra tạo nó lại cực kỳ đơn giản, mà giá trị của nó đem lại mới là thứ to lớn.
Đối với coder java chắc chắn đều đã từng sử dụng rất nhiều dạng này List, Set, Map<String, String>,… một kiểu dữ liệu đi kèm dấu <> bên trong đó lại là kiểu dữ liệu.
Liệu bạn có bao giờ thắc mắc nó là cái gì? Tại sao phải dùng đến nó? Nó được tạo ra như thế nào? Chúng ta sẽ đi lần lượt.
Đây là cái gì?
Đây là Generic trong java, một khái niệm xuất hiện từ bản JDK 5.0. Bình thường chúng ta có thể tham số hóa các giá trị truyền vào method bằng cách sử dụng biến, thì ở đây có thể tham số hóa kiểu dữ liệu bằng Generic.
Một thứ dành cho nhiều thằng khác sử dụng chung. Cái chung ở đây là bất kỳ thằng nào sử dụng nó để có thể sử dụng toàn bộ phương thức và thuộc tính nó cho phép mà không cần implement lại, bởi vì bản thân nó đã định nghĩa các xử lý trong đó. Nhưng nó vẫn có cái riêng, đó lại với những thằng sử dụng nó muốn truyền cho nó datatype là gì String/Integer/Long,… nó đều xử lý được hết mà không cần sửa gì hay implement lại cái gì của nó. Đây chính là tiền thân giá trị to lớn của nó.
Tại sao phải dùng đến nó?
Thử xem qua đoạn code dưới đây
public List getListValue(){ List lst = new ArrayList(); lst.add("string"); lst.add(2304); lst.add(new Object()); return lst; } public void useList(){ List lstInteger = getListValue(); Integer intVal = (Integer) lstInteger.get(0); }
Đoạn code này compile bình thường không có lỗi. Nhưng nhìn bằng mắt các bạn có thể nhận ra sẽ có lỗi Runtime khi chạy, đó là lỗi ép kiểu.
Phần tử đầu tiên của list là 1 chuỗi nhưng trong hàm useList thì đang ép nó sang Integer. Đây là 1 đoạn code ngắn thì các bạn có thể nhận ra ngay lỗi này, nhưng nếu hàm getListValue() là 1 nghiệp vụ rất phức tạp bên trong thì sẽ khó mà kiểm soát được vấn đề này.
Vậy nên Generic được sinh ra để xử lý việc đó, xem đoạn code dưới:
public List<Integer> getListValue(){ List<Integer> lst = new ArrayList<>(); lst.add("string"); //error lst.add(2304); lst.add(new Object()); //error return lst; } public void useList(){ List<Integer> lstInteger = getListValue(); Integer intVal = lstInteger.get(0); }
Hãy thử paste nó vào IDE nào đó như IntelliJ, Netbean, Eclipse,… bạn sẽ thấy IDE sẽ cảnh báo luôn cho bạn 2 lỗi trên, và hiển nhiên là sẽ không compile được chương trình. Cùng với đó là thao tác ép kiểu cho biến intVal trong hàm useList() cũng trở nên thừa thãi và có thể bỏ đi.
Như vậy từ 1 đoạn code tiềm ẩn rủi ro khi runtime thì ta đã chuyển nó thành lỗi compile để xử lý luôn. Và Generic được sinh ra để hạn chế lỗi ép kiểu
Mặc dù thấy nó đã khá hữu ích, nhưng trong thực tế ta cần làm việc với không ít kiểu dữ liệu, ta phải tạo ra cả class mới để làm kiểu dữ liệu cho riêng mình chứ không đơn thuần là List, Set, Map,.. nhưng lại vẫn muốn ứng dụng Generic để tránh lỗi tiềm ẩn, vậy cần làm thế nào? Ta sẽ sang phần kế tiếp.
Cách tạo một Generic mới:
Mình sẽ sử dụng bài toán ở Phần 1: Interface với cách tạo Listener lắng nghe sự kiện và Event kích hoạt sự kiện làm ví dụ để đồng thời các bạn cũng có thể thấy được lợi ích khác của Generic
Giả sử phần truy vấn Database của hàm onSearch trong ListStaffController và ListDepartmentController được viết mô phỏng như sau:
@Override public void onSearch(String keyword) { //Truy vấn bảng Nhân viên theo keyword để tìm nhân viên List<StaffDTO> lstStaff = getSession().createCriteria(StaffDTO.class).list();//Hibernate //Hiển thị danh sách nhân viên tìm được vào grid Nhân viên trên giao diện }
@Override public void onSearch(String keyword) { //Truy vấn bảng Phòng ban theo keyword để tìm phòng ban List<DepartmentDTO> lstDepartment = getSession().createCriteria(DepartmentDTO.class).list();//Hibernate //Hiển thị danh sách phòng ban tìm được vào grid Phòng ban trên giao diện }
Hai đoạn xử lý trên tuy khác nhau về đối tượng nhưng lại giống nhau về cách thức. Nếu sau này có nhiều chức năng khác nữa cũng cần cái SearchBox này thì việc viết lại hàm onSearch này nhiều lần cũng chưa hẳn đã ổn. Vậy nên ta có thể chuyển sang cách dùng Generic như sau:
package net.tunghuynh.generic; //Ký hiệu T trong dấu <> là cách khai báo tham số hóa 1 kiểu dữ liệu. //Ta có thể tham số hóa nhiều kiểu dữ liệu phân cách bằng dấu phảy "<T, V>" public abstract class SearchBoxController<T> { private String txtKeyword;//Textbox nhập từ khóa tìm kiếm private Class dtoType; //todo Khai báo các biến cần xử lý khác public SearchBoxController(){ //Lấy kiểu dữ liệu đã được tham số hóa Type t = getClass().getGenericSuperclass(); ParameterizedType pt = (ParameterizedType) t; dtoType = (Class) pt.getActualTypeArguments()[0];//ActualTypeArguments là mảng type được tham số hóa } public void onClickButtonSearch() { List<T> lstStaff = getSession().createCriteria(this.dtoType.getName()).list();//Hibernate afterSearch(lstStaff); } /** * Xử lý dữ liệu sau khi truy vấn, hiển thị dữ liệu lên giao diện * @param lstData */ public abstract void afterSearch(List<T> lstData); //todo Định nghĩa các method xử lý (gợi ý lịch sử, tìm kiếm theo giọng nói, .....) }
Bây giờ cách sử dụng trở nên gọn gàng hơn và chỉ cần quan tâm đến cách xử lý đầu ra của dữ liệu tìm kiếm.
package net.tunghuynh.generic; public class ListStaffController extends SearchBoxController<StaffDTO>{ public ListStaffController(){ super(); } @Override public void afterSearch(List<StaffDTO> lstData) { //Hiển thị danh sách nhân viên tìm được vào grid Nhân viên trên giao diện } }
package net.tunghuynh.generic; public class ListDepartmentController extends SearchBoxController<DepartmentDTO>{ public ListDepartmentController(){ super(); } @Override public void afterSearch(List<DepartmentDTO> lstData) { //Hiển thị danh sách phòng ban tìm được vào grid Phòng ban trên giao diện } }
Như vậy là các bạn đã tạo xong và sử dụng được 1 Generic đơn giản, làm tiền đề cho việc tìm hiểu hoạt động của các framework như Hibernate, Spring,….
Ở bài sau, mình sẽ đề cập đến khái niệm Wildcard cũng liên quan đến Generic
Chúc các bạn thành công!