Làm Quen Với Generics Trong Java

Hôm nay mình sẽ giới thiệu tới 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. Generic rất hữu dụng trong việc kiểm soát kiểu dữ liệu, vậy nó là gì?

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:

  1. T - Type (Kiểu dữ liệu bất kỳ thuộc Wrapper class: String, Integer, Long, Float, …)
  2. E – Element (phần tử – được sử dụng phổ biến trong Collection Framework)
  3. K – Key (khóa)
  4. V – Value (giá trị)
  5. N – Number (kiểu số: Integer, Double, Float, …)
  6. 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); //Ouput: Study: hoc
    }
}

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); // Ouput: Study: hoc
    }
}

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); // Ouput: Study: hoc
    }
}

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); // Ouput: Study: hoc
    }
}

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; // khởi tạo thêm tham số generic 

    public Book(K key, V value) {
        super(key, value);
    }

    public Book(K key, V value, I info) {
        super(key, value);
        this.info = info; // truyền Kiểu generic mới vào 
    }

    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);
        // Ouput:
        // Study: hoc
        // Quanity: 123
    }
}

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) {
        // do something
    }

    @Override
    public void delete(T obj) {
        // do something
    }

    @Override
    public void write(T obj) {
        // do something
    }

}

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 {
    // Arraylist sẽ chứa các phần tử E
    // ta sẽ trả về phần tử đầu tiền trong arr khi được gọi
    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));
        // output: Codelearn.io
    }
}

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(); // error

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; // khởi tạo đối tượng lưu tham số generic
    public MyGeneric(Class<T> classObject) throws InstantiationException, IllegalAccessException {
        // lấy tên Class và gán nó vào đối tượng obj
        this.obj = (T) classObject.newInstance();

    }
    public T getObj() {
        return obj; // trả về obj
    }
}

public class Demo {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        // khởi tạo đối tượng Codelearn trên myObj
        MyGeneric<Codelearn> myObj = new MyGeneric<Codelearn>(Codelearn.class);
        // bây giờ myObj đã có đối tượng Codelearn
        // t chỉ cần gọi đối tượng đó ra bằng method getObj()
        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]; // error
T obj[]; // ok

Để 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; // khởi tạo mảng

    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" };

        // truyền array names vào trong MyArrayGeneric để gắn mảng vào trong nó
        MyArrayGeneric<String> myArrayGeneric = new MyArrayGeneric<String>(names);
        // ta có thể dổi thành các kiểu khác như Double, Integer, ...

        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 {
    // drawShapes chỉ chấp nhận các kiểu thuộc lớp Shape hoặc con của nó
    public static void drawShapes(List<? extends Shape> lists) {
        for (Shape s : lists) {
            s.draw(); // call method của lớp Shape từ lớp con
        }
    }

    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.


  RATE: 4.2 

  948 VIEW


chưa có bình luận nào ...
BÀI VIẾT LIÊN QUAN
Checklist 8 Điều Bạn Cần Làm Để Đạt Điểm Cao Trước Khi Nộp Luận Văn / Đồ Án

Ở FPT, Làm đồ án đã khó thì viết báo cáo đồ án lại càng là việc không hề dễ dàng. Chuẩn hoá file word, excel, ppt là một bước quan trọng để đảm bảo nhóm bạn đạt được điểm số cao nhất. Trong bài viết này mình sẽ giới thiệu đến bạn 8 mẹo cần thiết để chuẩn hoá file luận văn/đồ án trước khi nộp cho nhà trường. Tìm hiểu ngay để có thể nộp một bản báo cáo chất lượng cao và ấn tượng với giảng viên của bạn.

BÀI VIẾT LIÊN QUAN
DRY principle trong lập trình - Làm thế nào để biết code của bạn DRY hay WET ?

Nhìn ảnh chắc chúng ta cũng đã hiểu, điều gì xảy ra nếu có quá nhiều đoạn code giống nhau trong chương trình của bạn. Có phải khi một yêu cầu nào đó thay đổi, bạn sẽ phải sửa lại hết toàn bộ những đoạn code giống nhau đó hay không. Vậy làm thế nào để giữ nguyên tắc DRY principle trong lập trình, ví dụ và tác hại của nó, các bạn hãy xem bài viết này nhé.

BÀI VIẾT LIÊN QUAN
Cách viết một Email chuyên nghiệp và lịch sự

Trong thời đại mạng xã hội phát triển như hiện nay, nhiều bạn vì ảnh hưởng từ việc nhắn tin bình thường thông qua Messenger hay Zalo, mà vô hình chung cho rằng những quy tắc trong viết Email không cần thiết nữa. Trong môi trường đại học của mình, trên trải nghiệm thực tế thì đa phần các bạn gửi Email rất thiếu chuyên nghiệp, mà đó là thứ ảnh hưởng không hề nhỏ tới công việc của bạn trong tương lai. Hôm nay trong bài viết, mình sẽ hướng dẫn cách viết một Email chuyên nghiệp và lịch sự nhất.