Generics là gì?
Khái niệm generics được đưa vào kể từ java 5.
Thuật ngữ “generics” được hiểu là tham số hóa kiểu dữ liệu. Việc tham số hóa kiểu dữ liệu giúp cho lập trình viên có thể dễ bắt lỗi các kiểu dữ liệu không hợp lệ, đồng thời giúp dễ dàng hơn cho việc tạo và sử dụng các class, interface, method với nhiều kiểu dữ liệu khác nhau.
Chúng ta sẽ mượn lớp ArrayList()
làm ví dụ:
Khi khai báo lớp ArrayList()
chúng ta thường gắn kiểu dữ liệu vào dấu <>
, để quy định kiểu dữ liệu của Lớp ArrayList() đó.
Sau khi khai báo ArrayList() ở kiểu Integer thì các phần tử trong ArrayList() chỉ có thể nhận kiểu Integer.
Ta cũng có thể đổi Integer thành các kiểu khác như Double, String, … mà không cần phải viết lại cả 1 class cho 1 kiểu khác. Việc này giúp việc tái sự dụng lại với Kiểu dữ liệu khác dễ dàng hơn và cũng không cần phải viết thêm 1 class mới để xử lý kiểu dữ liệu khác.
Đây là trường hợp chúng ta phải tạo nhiều class để xử lý nhiều kiểu dữ liệu khác nhau.
Một số quy ước trong generics
Có rất nhiều cách để đặt tên cho kiểu tham số trong Generic nhưng chúng ta nên tuân theo nhưng kiểu đặt tên tiêu chuẩn để sau này nếu có làm việc nhóm thì team sẽ dễ đọc code hơn.
Các kiểu tham số thông thường:
- T - Type (Kiểu dữ liệu bất kỳ thuộc Wrapper class: String, Integer, Long, Float, …)
- E – Element (phần tử – được sử dụng phổ biến trong Collection Framework)
- K – Key (khóa)
- V – Value (giá trị)
- N – Number (kiểu số: Integer, Double, Float, …)
- U,S,I,G, … (tùy theo kiểu của người dùng đặt)
Các kiểu Generic
1. Tạo class Generic với kiểu tham số generic
Dictionary<K, V>
là một class Generics nó chứa một cặp khóa và giá trị (key/ value)
.
class Dictionary<K, V> {
private K key;
private V value;
public Dictionary(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
K và V được gọi là 1 kiểu tham số chiếu nào đó của Dictionary<K,V>. Khi sử dụng class này chúng ta phải xác định 1 kiểu tham số cụ thể.
public class DemoGeneric {
public static void main(String[] args) {
Dictionary<String, String> d = new Dictionary<String, String>("Study", "hoc");
String english = d.getKey();
String vietnamese = d.getValue();
System.out.println(english + ": " + vietnamese);
}
}
2. Thừa kế lớp Generics
Một class mở rộng từ một class Generics, nó có thể quy định kiểu cho tham số Generics, giữ nguyên các tham số Generics hoặc thêm các tham số Generics.
2.1. Cách quy định kiểu cho tham số Generics.
Chúng ta tái sử dụng class Dictionary<K,V>.
class Book extends Dictionary<String, String> {
public Book(String key, String value) {
super(key, value);
}
}
public class Demo {
public static void main(String[] args) {
Book l = new Book("Study", "hoc");
String english = l.getKey();
String vietnamese = l.getValue();
System.out.println(english + ": " + vietnamese);
}
}
2.2. Cách quy định giữ nguyên các tham số generic.
class Book<K, V> extends Dictionary<K, V> {
public Book(K key, V value) {
super(key, value);
}
}
public class Demo {
public static void main(String[] args) {
Book<String, String> l = new Book<String, String>("Study", "hoc");
String english = l.getKey();
String vietnamese = l.getValue();
System.out.println(english + ": " + vietnamese);
}
}
Note: Chúng ta có thể vừa quy định trước 1 kiểu cụ thể vừa giữ nguyên tham số generic
class Book<V> extends Dictionary<String, V> {
public Book(String key, V value) {
super(key, value);
}
}
public class Demo {
public static void main(String[] args) {
Book<String> l = new Book<String>("Study", "hoc");
String english = l.getKey();
String vietnamese = l.getValue();
System.out.println(english + ": " + vietnamese);
}
}
2.3. Cách thêm các tham số Generics vào 1 Class generic khác.
tạo 1 class con kế thừa Dictionary<K,V> sau đó thêm tham số I vào
class Book<K, V, I> extends Dictionary<K, V> {
private I info;
public Book(K key, V value) {
super(key, value);
}
public Book(K key, V value, I info) {
super(key, value);
this.info = info;
}
public I getInfo() {
return info;
}
public void setInfo(I info) {
this.info = info;
}
}
public class Demo {
public static void main(String[] args) {
Book<String, String, Integer> l = new Book<String, String, Integer>("Study", "hoc", 123);
String english = l.getKey();
String vietnamese = l.getValue();
int quanity = l.getInfo();
System.out.println(english + ": " + vietnamese + "\nQuantity: " + quanity);
}
}
3, Generics Interface
Chúng ta khởi tạo 1 interface sau đó để 1 class implements lên là chúng ta đã có thể sử dụng.
interface writer<T> {
void update(T obj);
void delete(T obj);
void write(T obj);
}
class Book<T> implements writer<T> {
@Override
public void update(T obj) {
}
@Override
public void delete(T obj) {
}
@Override
public void write(T obj) {
}
}
public class Demo {
public static void main(String[] args) {
Book<String> t = new Book<String>();
t.write("Add data to book");
}
}
Các thao tác trên Generic
1. Khởi tạo phương thức generic
class Store {
public static <E> E getFirstElement(ArrayList<E> arr) {
if (arr.isEmpty())
return null;
E first = arr.get(0);
return first;
}
}
public class Demo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Codelearn.io");
list.add("Google.com");
list.add("Azure");
list.add("GCF");
System.out.println(Store.getFirstElement(list));
}
}
2. Khởi tạo đối tượng Generic
Chúng ta không được phép khởi tạo đối tương obj
theo kiểu này.
T obj = new T();
Vì khi khởi tạo <T> không hề tồn tại ở thời điểm java khởi chạy. Nó chỉ có ý nghĩa với trình biên dịch kiểm soát code của người lập trình. Mọi kiểu <T> đều là Object tại thời điểm chạy của Java.
package Generics;
class Codelearn {
public Codelearn() {
}
public void sayHi() {
System.out.println("chúc các bạn học tốt!");
}
}
class MyGeneric<T> {
private T obj;
public MyGeneric(Class<T> classObject) throws InstantiationException, IllegalAccessException {
this.obj = (T) classObject.newInstance();
}
public T getObj() {
return obj;
}
}
public class Demo {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
MyGeneric<Codelearn> myObj = new MyGeneric<Codelearn>(Codelearn.class);
myObj.getObj().sayHi();
}
}
Output:
3, Khởi tạo mảng Mảng Generic
Cũng tương tự như tạo đối tượng generic chúng ta không thể tạo theo cách thông thường, nhưng chúng ta có thể khai báo nó 1 cách bình thường.
T obj[] = new T[5];
T obj[];
Để khởi tạo mảng trình biên dịch của Java cần biết rõ <T> là cái gì mới có thể biên dịch (compile) new T[5]. Nếu không biết rõ nó mặc định coi <T> là Object.
Các khởi tạo mảng cũng tương tự như khi tạo đối tượng.
class MyArrayGeneric<T> {
private T[] array;
public MyArrayGeneric(T[] array) {
this.array = array;
}
public T[] getArray() {
return array;
}
public T getFirstElement() {
if (this.array.length == 0) {
return null;
}
return this.array[0];
}
public T getLastElement() {
if (this.array.length == 0) {
return null;
}
return this.array[this.array.length - 1];
}
}
public class Demo {
public static void main(String[] args) {
String[] names = new String[] { "Dat", "Khoa", "Tin" };
MyArrayGeneric<String> myArrayGeneric = new MyArrayGeneric<String>(names);
System.out.println("First name: " + myArrayGeneric.getFirstElement());
System.out.println("Last name: " + myArrayGeneric.getLastElement());
}
}
Generics với ký tự đại diện Generic method (wildcard):
Trong Generic, dấu chấm hỏi (?
) được gọi là một đại diện (wildcard
), nó là kiểu không xác định.
Wildcard có thể được sử dụng cho rất nhiều tính huống ví dụ như kiểu tham số, trường hoặc biến cục bộ; đôi khi là một kiểu trả về.
Tùy vào ví trí của Wildcard mà nó sẽ có những ý nghĩa khác nhau:
- Collection<?>: mô tả một tập hợp chấp nhận tất cả các loại đối số (chứa mọi kiểu đối tượng).
- List<? extends Number>: mô tả một danh sách, nơi mà các phần tử là kiểu Number hoặc kiểu con của Number.
- Comparator<? super String>: Mô tả một bộ so sánh (Comparator) mà thông số phải là String hoặc cha của String.
Ví dụ:
package Generics;
import java.util.ArrayList;
import java.util.List;
abstract class Shape {
abstract void draw();
}
class Circle extends Shape {
void draw() {
System.out.println("ve hinh tron");
}
}
class Square extends Shape {
void draw() {
System.out.println("ve hinh Vuong");
}
}
public class Demo {
public static void drawShapes(List<? extends Shape> lists) {
for (Shape s : lists) {
s.draw();
}
}
public static void main(String args[]) {
List<Square> list1 = new ArrayList<Square>();
list1.add(new Square());
List<Circle> list2 = new ArrayList<Circle>();
list2.add(new Circle());
list2.add(new Circle());
drawShapes(list1);
drawShapes(list2);
}
}
Output:
Tổng kết:
Trong bài này, mình đã giới thiệu các bạn những đặc điểm cơ bản của generic, cách tạo class generic
, cách khởi tạo phương thức, đối tượng và mảng trong generic. Hi vọng qua bài viết này các bạn có thể có một tầm nhìn khái quát về generic. Generic rất hữu dụng trong việc kiểm soát kiểu dữ liệu, đồng thời khi đi phỏng vấn cũng có nhiều câu hỏi về chủ đề này vì vậy các bạn cũng hãy tìm hiểu thêm từ các nguồn khác để cái thiển kĩ năng và trình độ của mình. Chúc các bạn thành công.