CodeGym/Blog Java/Ngẫu nhiên/Generics trong Java

Generics trong Java

Xuất bản trong nhóm
CHÀO! Chúng ta sẽ nói về Java Generics. Tôi phải nói rằng bạn sẽ học được rất nhiều! Không chỉ bài học này, mà cả những bài học tiếp theo sẽ được dành cho thuốc generic. Vì vậy, nếu bạn quan tâm đến thuốc generic, thì hôm nay là ngày may mắn của bạn: bạn sẽ học được rất nhiều điều về các đặc điểm của thuốc generic. Và nếu không, hãy từ chức và thư giãn! :) Đây là một chủ đề rất quan trọng, và bạn cần phải biết nó. Hãy bắt đầu với điều đơn giản: "cái gì" và "tại sao".

Java Generics là gì?

Generics là các loại có một tham số. Khi tạo một kiểu chung, bạn chỉ định không chỉ một kiểu mà còn cả kiểu dữ liệu mà nó sẽ làm việc. Tôi đoán ví dụ rõ ràng nhất đã xuất hiện trong đầu bạn: ArrayList! Đây là cách chúng ta thường tạo một cái trong một chương trình:
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Như bạn có thể đoán, một đặc điểm của danh sách này là chúng ta không thể nhét mọi thứ vào đó: nó chỉ hoạt động với các đối tượng Chuỗi . Bây giờ chúng ta hãy đi lạc đề một chút về lịch sử của Java và cố gắng trả lời câu hỏi "tại sao?" Để làm điều này, chúng ta sẽ viết phiên bản đơn giản hóa của lớp ArrayList. Danh sách của chúng tôi chỉ biết cách thêm dữ liệu vào và truy xuất dữ liệu từ một mảng bên trong:
public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Giả sử chúng tôi muốn danh sách của mình chỉ lưu trữ Integer s. Chúng tôi không sử dụng một loại chung chung. Chúng tôi không muốn bao gồm kiểm tra "instanceof Integer " rõ ràng trong phương thức add() . Nếu chúng ta làm như vậy, toàn bộ lớp của chúng ta sẽ chỉ phù hợp với Integer và chúng ta sẽ phải viết một lớp tương tự cho mọi loại dữ liệu khác trên thế giới! Chúng tôi sẽ dựa vào các lập trình viên của mình và chỉ để lại nhận xét trong mã để đảm bảo rằng họ không thêm bất kỳ thứ gì chúng tôi không muốn:
// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Một trong những lập trình viên đã bỏ lỡ nhận xét này và vô tình đặt một số Chuỗi vào danh sách các số rồi tính tổng của chúng:
public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Đầu ra bảng điều khiển:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
      at Main.main (Main.java:14)
Phần tồi tệ nhất của tình huống này là gì? Chắc chắn không phải là sự bất cẩn của lập trình viên. Phần tồi tệ nhất là mã không chính xác đã kết thúc ở một vị trí quan trọng trong chương trình của chúng tôi và được biên dịch thành công. Bây giờ chúng ta sẽ gặp lỗi không phải trong khi viết mã mà chỉ trong quá trình thử nghiệm (và đây là trường hợp tốt nhất!). Sửa lỗi trong các giai đoạn phát triển sau này tốn nhiều chi phí hơn — cả về tiền bạc và thời gian. Đây chính xác là điểm chung mang lại lợi ích cho chúng ta: một lớp chung cho phép lập trình viên kém may mắn phát hiện ra lỗi ngay lập tức. Chương trình đơn giản là sẽ không biên dịch!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();

       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Lập trình viên ngay lập tức nhận ra sai lầm của mình và ngay lập tức trở nên tốt hơn. Nhân tiện, chúng tôi không phải tạo lớp Danh sách của riêng mình để thấy loại lỗi này. Chỉ cần xóa dấu ngoặc nhọn và nhập ( <Integer> ) từ ArrayList thông thường!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Đầu ra bảng điều khiển:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
     at Main.main(Main.java:16)
Nói cách khác, ngay cả khi sử dụng các cơ chế "gốc" của Java, chúng ta vẫn có thể mắc lỗi này và tạo ra một bộ sưu tập không an toàn. Tuy nhiên, nếu chúng tôi dán mã này vào IDE, chúng tôi sẽ nhận được cảnh báo: "Cuộc gọi chưa được kiểm tra để thêm (E) với tư cách là thành viên của loại java.util.List thô" Chúng tôi được thông báo rằng có thể xảy ra sự cố khi thêm một mục đến một bộ sưu tập thiếu loại chung. Nhưng cụm từ "loại thô" có nghĩa là gì? Loại thô là một lớp chung có loại đã bị xóa. Nói cách khác, Danh sách myList1 là một loại thô . Đối lập với loại thôloại chung - một lớp chung với chỉ báo về (các) loại được tham số hóa . Ví dụ: Danh sách<Chuỗi> myList1. Bạn có thể hỏi tại sao ngôn ngữ cho phép sử dụng các loại nguyên ? Lý do rất đơn giản. Những người tạo ra Java đã để lại hỗ trợ cho các kiểu thô trong ngôn ngữ để tránh tạo ra các vấn đề về tính tương thích. Vào thời điểm Java 5.0 được phát hành (generic lần đầu tiên xuất hiện trong phiên bản này), rất nhiều mã đã được viết bằng cách sử dụng các loại thô . Do đó, cơ chế này vẫn được hỗ trợ cho đến ngày nay. Chúng tôi đã nhiều lần đề cập đến cuốn sách kinh điển "Java hiệu quả" của Joshua Bloch trong các bài học. Là một trong những người tạo ra ngôn ngữ này, ông đã không bỏ qua các kiểu thôcác kiểu chung trong cuốn sách của mình. Generics trong Java là gì?  - 2Chương 23 của cuốn sách có một tiêu đề rất hùng hồn: "Không sử dụng các loại thô trong mã mới" Đây là điều bạn cần ghi nhớ. Khi sử dụng các lớp chung, đừng bao giờ biến một loại chung thành một loại thô .

phương pháp chung

Java cho phép bạn tham số hóa các phương thức riêng lẻ bằng cách tạo cái gọi là các phương thức chung. Những phương pháp như vậy hữu ích như thế nào? Trên hết, chúng hữu ích ở chỗ chúng cho phép bạn làm việc với các loại tham số phương thức khác nhau. Nếu cùng một logic có thể được áp dụng một cách an toàn cho các loại khác nhau, thì một phương pháp chung có thể là một giải pháp tuyệt vời. Hãy coi đây là một ví dụ rất đơn giản: Giả sử chúng ta có một danh sách gọi là myList1 . Chúng tôi muốn xóa tất cả các giá trị khỏi danh sách và lấp đầy tất cả các khoảng trống bằng các giá trị mới. Đây là giao diện của lớp chúng ta với một phương thức chung:
public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Hãy chú ý đến cú pháp. Có vẻ hơi bất thường:
public static <T> void fill(List<T> list, T val)
Chúng tôi viết <T> trước kiểu trả về. Điều này chỉ ra rằng chúng ta đang xử lý một phương pháp chung. Trong trường hợp này, phương thức chấp nhận 2 tham số làm đầu vào: danh sách các đối tượng T và một đối tượng T riêng biệt khác. Bằng cách sử dụng <T>, chúng tôi tham số hóa các loại tham số của phương thức: chúng tôi không thể chuyển vào danh sách Chuỗi và Số nguyên. Một danh sách các Chuỗi và một Chuỗi, một danh sách các Số nguyên và một Số nguyên, một danh sách các đối tượng Cat của chính chúng ta và một đối tượng Cat khác — đó là những gì chúng ta cần làm. Phương thức main() minh họa cách có thể dễ dàng sử dụng phương thức fill() để làm việc với các loại dữ liệu khác nhau. Đầu tiên, chúng tôi sử dụng phương thức với danh sách Chuỗi và Chuỗi làm đầu vào, sau đó với danh sách Số nguyên và Số nguyên. Đầu ra bảng điều khiển:
[New String, New String, New String] [888, 888, 888]
Hãy tưởng tượng nếu chúng ta không có các phương thức chung và cần logic của phương thức fill() cho 30 lớp khác nhau. Chúng ta sẽ phải viết cùng một phương thức 30 lần cho các loại dữ liệu khác nhau! Nhưng nhờ các phương thức chung, chúng ta có thể sử dụng lại mã của mình! :)

lớp học chung

Bạn không bị giới hạn trong các lớp chung được cung cấp trong các thư viện Java tiêu chuẩn — bạn có thể tạo riêng cho mình! Đây là một ví dụ đơn giản:
public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());

       stringBox.set(12345); // Compilation error!
   }
}
Lớp Box<T> của chúng tôi là một lớp chung. Sau khi chúng tôi chỉ định một loại dữ liệu ( <T> ) trong khi tạo, chúng tôi không còn có thể đặt các đối tượng thuộc các loại khác trong đó. Điều này có thể được nhìn thấy trong ví dụ. Khi tạo đối tượng của chúng tôi, chúng tôi đã chỉ ra rằng nó sẽ hoạt động với Chuỗi:
Box<String> stringBox = new Box<>();
Và trong dòng mã cuối cùng, khi chúng tôi cố gắng đặt số 12345 vào trong hộp, chúng tôi gặp lỗi biên dịch! Nó là dễ dàng! Chúng tôi đã tạo lớp chung của riêng mình! :) Như vậy là bài học hôm nay đã kết thúc. Nhưng chúng tôi không nói lời tạm biệt với thuốc generic! Trong các bài học tiếp theo, chúng ta sẽ nói về các tính năng nâng cao hơn, vì vậy đừng bỏ lỡ! ) Để củng cố những gì bạn đã học, chúng tôi khuyên bạn nên xem một video bài học từ Khóa học Java của chúng tôi
Chúc bạn thành công trong học tập! :)
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào