Note: Tất cả code trong bài được code trực tiếp lúc viết blog, chưa kiểm tra lại với IDE, nên nếu có đoạn nào ko compile vì thiếu ;
hay {}
thì mong bạn bỏ qua.
Nội dung bài viết
I. Form cơ bản
Khai báo như sau:
class class_name<type-param-list> {...}
Ví dụ:
class Person<T> { private T id; public Person(T id){ this.id = id; } public T getId(){ return id; } }
T
ở đây đóng vai trò là placeholder, giới thiệuT
cho các instance methods, variables biết rằng có xuất hiện của Type parameter.- Khi báo biến và khởi tạo object, bạn phải thay thế
T
bằng 1 type cụ thể (String, Integer, Bigdecimal …)
Person<String> p1 = new Person<>("abc"); //or Person<Integer> p2 = new Person<>(123); //or Person<BigDecimal> p3 = new Person<>(new Bigdecimal("12"));
Bạn có thể add nhiều hơn 1 Type parameter, ví dụ:
class Person<T, U> { private T id; private U detail; ... }
Tương tự, bạn phải thay thế T, U
bằng những type cụ thể khi dùng.
Person<String, Detail> p1 = new Person<>("abc", new Detail());
Detail
là 1 class (giả định đã viết ở 1 chỗ khác).
II. Raw type
Đôi khi bạn thấy thư viện hoặc ở đâu đó dùng tên class mà không điền Actual Type Parameter, cái đó gọi là raw type. Ví dụ:
Person p1 = new Person("abc");
- Trước Generics, ngta dùng
List
nhưng ko type-safety nên ngta mới thêm generics vào, thànhList<String>
, giờ bạn muốn quay trở về trước đó thì bạn lại dùng raw typeList
- Khi đã biết kiểu này, ko có type-safety và không còn dấu
<>
Raw type dùng khi nào? Dưới đây là 1 ví dụ:
public interface Interview<T>{...} class Person implements Interview {...} //raw type //vì bạn chưa biết T ở đây là type nào.
III. Bounded type
Nếu T
có thể là bất cứ type gì thì rộng quá, có cách nào để giới hạn T này lại không, như t chỉ muốn T
là các type con của type WebDriver chẳng hạn. Ok then, bạn có thể sử dụng bounded type để giới hạn lại.
class Box<T extends superclass> {...} class Box<T super superclass> {...}
bạn sẽ dùng keyword extends
hoặc super
để tạo ra giới hạn.
Ví dụ:
class Box<T extends WebDriver> { private T driver; public Person(T driver){ this.driver = driver; } public void open(String url){ driver.get(url); } }
T extends WebDriver
, compiler biết T là class con của WebDriver, nên compiler cho phép biến driver
được sử dụng các method của WebDriver, ở đây mình chỉ dùng get(url)
Cách sử dụng như sau:
Box<WebDriver> box = new Box<>(new ChromeDriver()); box.open("https://google.com");
Ngoài ra, bạn có thể extends nhiều class và inteface.
class Box<T extends MyClass & MyInterface> {...}
T
ở đây phải là class con của MyClass
và implements MyInterface
. Cái này gọi là intersection type.
IV. Wildcard type
Type parameter có nghĩa là bạn sẽ phải cho biết type chính xác khi dùng. Nhưng đôi khi bạn cần truyền generics type vào thành 1 parameter của 1 method và cái generics type này phải có khả năng match vào mọi parameterized type.
Ví dụ 1: Print 1 list bất kỳ
Nếu bạn nghĩ răng có thể chọn List<Object>
để thể hiện là 1 list bất kỳ thì bạn đã nhầm.
@Test void name() { List<Number> numbers = new ArrayList<>(List.of(1, 2, 42.1, 2)); print(numbers); //compile error } public void print(List<Object> list) { System.out.println(list); }
Vì trong vd trên List<Number>
không phải là con của List<Object>
Để fix vấn đề trên bạn có 2 cách:
- Thêm type parameter
T
cho method
public <T> void print(List<T> list) { System.out.println(list); }
- Hoặc sử dụng wildcard
<?>
public void print(List<?> list) { System.out.println(list); }
Ví dụ 2: so sánh giá trị trung bình của 2 tập hợp (Integer và Double) khác nhau
Yêu cầu: iob.compareAvg(dob)
: compare average của 2 generics type chưa biết.
public class Stat<T extends Number> { T[] numbers; public Stat(T[] numbers) { this.numbers = numbers; } double average() { double sum = 0.0; for (T number : numbers) { sum += number.doubleValue(); } return sum / numbers.length; } } public class Demo { public static void main(String[] args) { Integer[] integers = {1, 2, 3, 4, 5}; Double[] doubles = {1.1, 2.2, 3.3, 4.4, 5.5}; Stat<Integer> iob = new Stat<>(integers); Stat<Double> dob = new Stat<>(doubles); if (iob.compareAvg(dob)) { //chưa viết method này System.out.println("same"); } else { System.out.println("differ"); } } }
Solution 1: Dùng type parameter T
, ta viết method như sau:
public boolean compareAvg(Stat<T> dob) { return average() == dob.average(); }
Khi viết như vậy thì, compiler sẽ yêu cầu cả 2 Stat
phải có type parameter giống nhau thì mới compare được, Stat<Integer> và Stat<Integer>
hoặc Stat<Double> và Stat<Double>
. Nếu bạn cố tình so sánh Stat<Integer>
vs Stat<Integer>
thì sẽ bị compile error.
Do đó, type parameter không dùng được cho TH này.
Solution 2: Dùng wildcard type.
public boolean compareAvg(Stat<?> dob) { return average() == dob.average(); }
?
là wildcard, bạn kết hợp với 1 class/interface nào đó thì bạn sẽ có wildcard type. Wildcard là cách để bạn thể hiện unknown type, vì nó là unknown nên nó có khả năng match vào bất cứ parameterized type có thể là Stat<Integer>
hoặc Stat<Double>
.
Dó đó, wildcard sẽ giúp ta giải quyết được bài toán trên, so sánh giá trị trung bình của 2 tập hợp khác nhau.