Phương thức Generic là các phương thức cho phép sử dụng tham số kiểu dữ liệu tương tự như với kiểu dữ liệu Generic.
Cú pháp
Cú pháp của phương thức generic giống như với phương thức bình thường, khác ở chỗ chúng ta thêm cặp dấu <>
và danh sách các tham số kiểu dữ liệu vào giữa cặp dấu đó được đặt trước kiểu dữ liệu trả về.
Ví dụ chúng ta khai báo lớp Util
và phương thức compare()
theo kiểu Generic như sau:
public class Util { public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } } public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; } }
Trong đoạn code trên, chúng ta định nghĩa lớp Pair
là một lớp generic lưu 2 thuộc tính key
và value
có kiểu dữ liệu bất kì, lớp Util
có một phương thức generic là compare()
nhận vào 2 đối tượng Pair
và so sánh xem 2 đối tượng này có trùng key
và value
hay không.
Cú pháp gọi phương thức generic compare()
như sau:
Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "berry"); boolean same = Util.<Integer, String>compare(p1, p2);
Ở đây chúng ta khai báo tên các kiểu dữ liệu của các đối tượng được truyền vào phương thức compare()
ngay trước tên phương thức.
Tuy nhiên chúng ta vẫn có thể không cần khai báo và để Java tự nhận biết các kiểu này:
Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "pear"); boolean same = Util.compare(p1, p2);
Giới hạn các kiểu dữ liệu được phép truyền
Chúng ta có thể giới hạn chỉ cho phép một số kiểu dữ liệu nào đó được truyền vào khi khai báo kiểu dữ liệu generic hoặc phương thức generic. Để làm việc này thì chúng ta thêm
extends <tên lớp>
vào sau tham số biến, chẳng hạn <T extends Integer>
, và như thế chúng ta sẽ chỉ có thể truyền vào tham số biến là lớp Integer
và các lớp con của lớp Integer
.
Chẳng hạn chúng ta có đoạn code như sau:
public class Box { public <T> void print(T t) { System.out.println(t + " is a " + t.getClass().getName()); } public static void main(String[] args) { Box box = new Box(); box.<Integer>print(new Integer(10)); } }
Trong đoạn code trên chúng ta định nghĩa lớp Box
có một phương thức generic nhận vào một tham số, bên trong phương thức này chúng ta in ra giá trị của tham số đó và tên lớp của tham số.
10 is a java.lang.Integer
Đoạn code trên rất đơn giản, nhưng bây giờ chúng ta sửa lại để lớp Box chỉ nhận các đối tượng Number
và các lớp con của Number
như sau:
public class Box { public <T extends Number> void print(T t) { System.out.println(t + " is a " + t.getClass().getName()); } public static void main(String[] args) { Box box = new Box(); box.<Integer>print(new Integer(10)); // OK box.<String>print(new String("Pho Code")); // error } }
Và như thế là chúng ta chỉ có thể truyền vào lớp Number
hoặc các lớp con của lớp Number
. Nếu truyền vào các kiểu dữ liệu khác thì Java sẽ báo lỗi.
10 is a java.lang.Integer Box.java:9: error: method print in class Box cannot be applied to given types; box.<String>print(new String("Pho Code")); ^ required: T found: String reason: explicit type argument String does not conform to declared bound(s) Number where T is a type-variable: T extends Number declared in method <T>print(T) 1 error
Và cũng tương tự chúng ta cũng có thể giới hạn các kiểu dữ liệu được phép dùng cho lớp generic chứ không chỉ có phương thức generic.
Giới hạn nhiều lớp
Nếu muốn giới hạn cho nhiều lớp hơn thì chúng ta ghi tên các lớp cơ sở cách nhau bởi dấu '&'
:
<T extends Integer & Boolean & Byte>
Sự thừa kế trong các kiểu generic
Như chúng ta đã biết, chúng ta có thể gán các đối tượng có kiểu dữ liệu là kiểu cha cho một đối tượng khác có kiểu dữ liệu là kiểu con. Chẳng hạn như chúng ta có thể gán một đối tượng Object
cho một đối tượng Integer
vì Object
là cha của Integer
:
Object someObject = new Object(); Integer someInteger = new Integer(10); someObject = someInteger; // OK
Đây còn được gọi là “mối quan hệ is a” trong lập trình hướng đối tượng. Khái niệm này cũng được áp dụng cho kiểu dữ liệu generic:
Box<Number> box = new Box<Number>(); box.add(new Integer(10)); // OK box.add(new Double(10.1)); // OK
Bây giờ chúng ta xem đoạn code sau:
public void print(Box<Number> n) { /* ... */ }
Đối với phương thức trên thì chúng ta phải truyền vô một đối tượng có kiểu chính xác là
Box<Number>
Các kiểu khác như Box<Integer>
hay Box<Double>
đều sẽ bị báo lỗi bởi vì các kiểu Box<Integer>
hay Box<Double>
… đều không phải là kiểu con của Box<Number>
mặc dù Integer
hay Double
là kiểu con của Number
.
Đây là một đặc điểm rất dễ bị nhầm lẫn khi lập trình generic. Để có thể truyền các kiểu Box<Integer>
hay Box<Double>
thì chỉ có một cách duy nhất là thực hiện việc kế thừa lại các lớp cha.