Java generics phần 2 – Syntax

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.

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ệu T 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ành List<String>, giờ bạn muốn quay trở về trước đó thì bạn lại dùng raw type List
  • 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.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments