Computer >> Máy Tính >  >> Lập trình >> Java

Hướng dẫn sử dụng Java Generics - Generics là gì và Cách sử dụng chúng?

Java Generics là một trong những tính năng quan trọng nhất của ngôn ngữ Java. Ý tưởng đằng sau generic khá đơn giản, tuy nhiên, đôi khi nó phức tạp do sự thay đổi từ cú pháp thông thường liên quan đến nó.

Mục đích của hướng dẫn này là giới thiệu cho bạn khái niệm hữu ích về generic một cách dễ hiểu.

Nhưng trước khi đi sâu vào bản thân các phần tử chung, hãy tìm hiểu lý do tại sao các phần tử chung của Java lại cần thiết ngay từ đầu.

Mục đích của Java Generics

Trước khi giới thiệu generics trong Java 5, bạn có thể viết và biên dịch một đoạn mã như thế này mà không gặp lỗi hoặc cảnh báo:

List list = new ArrayList();
list.add("hey");
list.add(new Object());

Bạn có thể thêm các giá trị thuộc bất kỳ loại nào vào một danh sách hoặc một Bộ sưu tập Java khác mà không cần phải khai báo loại dữ liệu mà nó lưu trữ. Nhưng khi bạn truy xuất các giá trị từ danh sách, bạn phải chuyển nó thành một kiểu nhất định.

Xem xét lặp lại danh sách trên.

for (int i=0; i< list.size(); i++) {
    String value = (String) list.get(i);  //CastClassException when i=1
}

Việc cho phép tạo danh sách mà không cần khai báo kiểu dữ liệu được lưu trữ trước, như chúng tôi đã làm, có thể dẫn đến việc lập trình viên mắc những lỗi như trên, điều này sẽ ném ra ClassCastExceptions trong thời gian chạy.

Generics đã được giới thiệu để ngăn các lập trình viên mắc phải những sai lầm như vậy.

Với generics, bạn có thể khai báo rõ ràng kiểu dữ liệu sẽ được lưu trữ khi tạo Bộ sưu tập Java như ví dụ sau cho thấy.

Lưu ý:Bạn vẫn có thể tạo đối tượng bộ sưu tập Java mà không cần chỉ định kiểu dữ liệu được lưu trữ nhưng không được khuyến khích.
List<String> stringList = new ArrayList<>();

Bây giờ, bạn không thể lưu trữ nhầm một Số nguyên trong danh sách kiểu Chuỗi mà không gây ra lỗi thời gian biên dịch. Điều này đảm bảo rằng chương trình của bạn không gặp lỗi thời gian chạy.

stringList.add(new Integer(4)); //Compile time Error

Mục đích chính của việc giới thiệu các generic vào Java là để tránh chạy vào ClassCastExceptions trong thời gian chạy.

Tạo Java Generics

Bạn có thể sử dụng generic để tạo các lớp và phương thức Java. Hãy xem các ví dụ về cách tạo các chỉ số chung của từng loại.

Loại Chung

Khi tạo một lớp chung, tham số kiểu cho lớp được thêm vào cuối tên lớp trong góc <> dấu ngoặc.

public class GenericClass<T> {
    private T item;
    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return this.item;
    }
}

Đây, T là tham số kiểu dữ liệu. T , NE là một số ký tự được sử dụng cho các tham số kiểu dữ liệu theo quy ước Java.

Trong ví dụ trên, bạn có thể chuyển cho nó một kiểu dữ liệu cụ thể khi tạo đối tượng GenericClass.

public static void main(String[] args) {

    GenericClass<String> gc1 = new GenericClass<>();
    gc1.setItem("hello");
    String item1 = gc1.getItem(); // "hello"
    gc1.setItem(new Object()); //Error

    GenericClass<Integer> gc2 = new GenericClass<>();
    gc2.setItem(new Integer(1));
    Integer item2 = gc2.getItem(); // 1
    gc2.setItem("hello"); //Error
}

Bạn không thể chuyển một kiểu dữ liệu nguyên thủy cho tham số kiểu dữ liệu khi tạo một đối tượng lớp chung. Chỉ các kiểu dữ liệu mở rộng Kiểu đối tượng mới có thể được chuyển dưới dạng tham số kiểu.

Ví dụ:

GenericClass<float> gc3 = new GenericClass<>(); //Error

Phương pháp Chung

Tạo các phương thức chung theo một mẫu tương tự như tạo các lớp chung. Bạn có thể triển khai một phương thức chung bên trong một lớp chung cũng như một phương thức không chung chung.

public class GenericMethodClass {

    public static <T> void printItems(T[] arr){
        for (int i=0; i< arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    public static void main(String[] args) {
        String[] arr1 = {"Cat", "Dog", "Mouse"};
        Integer[] arr2 = {1, 2, 3};

        GenericMethodClass.printItems(arr1); // "Cat", "Dog", "Mouse"
        GenericMethodClass.printItems(arr2); // 1, 2, 3
    }
}

Tại đây, bạn có thể truyền một mảng của một kiểu cụ thể để tham số hóa phương thức. Phương thức chung PrintItems() lặp qua mảng đã truyền và in các mục được lưu trữ giống như một phương thức Java thông thường.

Tham số loại bị ràng buộc

Cho đến nay, các lớp và phương thức chung mà chúng ta đã tạo ở trên có thể được tham số hóa cho bất kỳ kiểu dữ liệu nào khác với kiểu nguyên thủy. Nhưng điều gì sẽ xảy ra nếu chúng ta muốn giới hạn các kiểu dữ liệu có thể được chuyển cho generic? Đây là nơi xuất hiện các tham số kiểu giới hạn.

Bạn có thể ràng buộc các kiểu dữ liệu được chấp nhận bởi một lớp hoặc phương thức chung bằng cách chỉ định rằng nó phải là một lớp con của một kiểu dữ liệu khác.

Ví dụ:

//accepts only subclasses of List
public class UpperBoundedClass<T extends List>{
    //accepts only subclasses of List
    public <T extends List> void UpperBoundedMethod(T[] arr) {
    }
}

Đây, UpperBoundedClassUpperBoundedMethod chỉ có thể được tham số hóa bằng các kiểu con của List kiểu dữ liệu.

List kiểu dữ liệu hoạt động như một giới hạn trên cho tham số kiểu. Nếu bạn cố gắng sử dụng kiểu dữ liệu không phải là kiểu con của List , nó sẽ gây ra lỗi thời gian biên dịch.

Giới hạn không chỉ giới hạn ở các lớp học. Bạn cũng có thể vượt qua các giao diện. Trong trường hợp này, việc mở rộng giao diện có nghĩa là triển khai giao diện.

Một tham số cũng có thể có nhiều giới hạn như ví dụ này cho thấy.

//accepts only subclasses of both Mammal and Animal
public class MultipleBoundedClass<T extends Mammal & Animal>{

    //accepts only subclasses of both Mammal and Animal
    public <T extends Mammal & Animal> void MultipleBoundedMethod(T[] arr){

    }
}

Kiểu dữ liệu chấp nhận phải là một lớp con của cả hai lớp Động vật và Động vật có vú. Nếu một trong những giới hạn này là một lớp, nó phải đứng đầu tiên trong khai báo ràng buộc.

Trong ví dụ trên, nếu Mammal là một lớp và Animal là một giao diện, thì Mammal phải đến trước như hình trên. Nếu không, mã sẽ gây ra lỗi thời gian biên dịch.

Ký tự đại diện Java Generics

Các ký tự đại diện được sử dụng để chuyển các tham số của kiểu chung cho các phương thức. Không giống như một phương thức chung, ở đây, tham số chung được chuyển cho các tham số được phương thức chấp nhận, khác với tham số kiểu dữ liệu mà chúng ta đã thảo luận ở trên. Một ký tự đại diện được đại diện bởi? biểu tượng.

public void printItems(List<?> list) {
    for (int i=0; i< list.size(); i++) {
        System.out.println(list.get(i));
    }
}

printItems() ở trên phương thức chấp nhận danh sách của bất kỳ kiểu dữ liệu nào làm tham số. Điều này ngăn các lập trình viên không phải lặp lại mã cho danh sách các kiểu dữ liệu khác nhau, trường hợp này sẽ không có số liệu chung.

Ký tự đại diện được giới hạn trên

Nếu chúng ta muốn giới hạn các kiểu dữ liệu được lưu trữ trong danh sách được phương thức chấp nhận, chúng ta có thể sử dụng các ký tự đại diện có giới hạn.

Ví dụ:

public void printSubTypes(List<? extends Color> list) {
    for (int i=0; i< list.size(); i++) {
        System.out.println(list.get(i));
    }
}

printSubTypes() phương thức chỉ chấp nhận các danh sách lưu trữ các kiểu con của Màu. Nó chấp nhận danh sách các đối tượng RedColor hoặc BlueColor, nhưng không chấp nhận danh sách các đối tượng Động vật. Điều này là do Động vật không phải là một loại phụ của Màu sắc. Đây là ví dụ về ký tự đại diện giới hạn trên.

Ký tự đại diện được giới hạn dưới

Tương tự, nếu chúng ta có:

public void printSuperTypes(List<? super Dog> list) {
    for (int i=0; i< list.size(); i++) {
        System.out.println(list.get(i));
    }
}

sau đó, printSuperTypes() phương thức chỉ chấp nhận danh sách lưu trữ các loại siêu của lớp Dog. Nó sẽ chấp nhận một danh sách các đối tượng Động vật có vú nhưng không phải là một danh sách các đối tượng LabDog vì LabDog không phải là một lớp cha của Chó mà là một lớp con. Đây là ví dụ về ký tự đại diện giới hạn dưới.

Kết luận

Java Generics đã trở thành một tính năng mà các lập trình viên không thể thiếu kể từ khi được giới thiệu.

Sự phổ biến này là do tác động của nó trong việc giúp cuộc sống của các lập trình viên trở nên dễ dàng hơn. Ngoài việc ngăn họ mắc lỗi mã hóa, việc sử dụng generic làm cho mã ít lặp lại hơn. Bạn có nhận thấy cách nó tổng quát hóa các lớp và phương thức để tránh phải lặp lại mã cho các kiểu dữ liệu khác nhau không?

Nắm bắt tốt các khái niệm chung là điều quan trọng để trở thành một chuyên gia về ngôn ngữ. Vì vậy, áp dụng những gì bạn đã học trong hướng dẫn này vào mã thực tế là cách để tiếp tục ngay bây giờ.