JavaRush /Blog Java /Random-VI /Generics trong Java là gì

Generics trong Java là gì

Xuất bản trong nhóm
Xin chào! Hôm nay chúng ta sẽ nói về thuốc generic. Tôi phải nói rằng bạn sẽ học được rất nhiều điều mới! Không chỉ điều này, mà một số bài giảng tiếp theo cũng sẽ được dành cho thuốc generic. Generics trong Java là gì - 1 Vì vậy, nếu chủ đề này thú vị với bạn thì bạn thật may mắn: hôm nay bạn sẽ học được rất nhiều điều về các tính năng của thuốc generic. Chà, nếu không, hãy bình tĩnh 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 một câu hỏi đơn giản: “cái gì” và “tại sao”. Thuốc generic là gì? Generics là loại có tham số. Khi tạo một cái chung, bạn không chỉ chỉ định loại của nó mà còn cả loại dữ liệu mà nó sẽ hoạt động. Tôi nghĩ bạn đã nghĩ đến ví dụ rõ ràng nhất - đây là ArrayList! Đây là cách chúng tôi thường tạo nó trong 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, điểm đặc biệt của danh sách là sẽ không thể “nhét” mọi thứ vào đó: nó chỉ hoạt động với các đối tượng String. Bây giờ chúng ta hãy thực hiện một chuyến tham quan ngắn 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, bản thân chúng ta sẽ viết một phiên bản đơn giản của lớp ArrayList. Danh sách của chúng tôi chỉ có thể thêm dữ liệu vào mảng bên trong và nhận dữ liệu này:
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 ta muốn danh sách của mình chỉ lưu trữ các số Integer. Chúng tôi không có thuốc generic. Chúng tôi không thể chỉ định rõ ràng phiên bản kiểm tra o Integertrong tệp add(). Khi đó 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 cùng một lớp cho tất cả các kiểu dữ liệu hiện có trên thế giới! Chúng tôi quyết định 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ã để họ không thêm bất kỳ điều gì không cần thiết vào đó:
//use it ONLY with 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ỏ sót nhận xét này và đã vô tình cố gắng đưa các số trộn với chuỗi vào danh sách, 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 của bảng điều khiển: 300 Ngoại lệ trong luồng "chính" java.lang.ClassCastException: java.lang.String không thể chuyển sang java.lang.Integer tại Main.main(Main.java:14) Điều gì tệ nhất trong tình huống này? Khác xa với sự thiếu chú ý của một lập trình viên. Điều tồi tệ nhất là mã sai lại nằm ở 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ẽ thấy lỗi không phải ở giai đoạn mã hóa mà chỉ ở giai đoạn thử nghiệm (và đây là trường hợp tốt nhất!). Việc sửa lỗi sau này trong quá trình phát triển sẽ tốn kém hơn rất nhiều - cả về tiền bạc và thời gian. Đây chính xác là lợi thế của generics: một lớp generic sẽ cho phép một lập trình viên không may mắn phát hiện ra lỗi ngay lập tức. Đơn giản là mã sẽ không được 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 sẽ ngay lập tức “tỉnh ngộ” và tự sửa lỗi ngay lập tức. Nhân tiện, chúng tôi không cần phải tạo lớp riêng Listđể thấy loại lỗi này. Chỉ cần xóa dấu ngoặc loại ( <Integer>) khỏi 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 của bảng điều khiển: 300 Ngoại lệ trong luồng "main" java.lang.ClassCastException: java.lang.String không thể được chuyển sang java.lang.Integer tại Main.main(Main.java:16) Nghĩa là, ngay cả khi sử dụng các công cụ "gốc" Java, bạn có thể mắc lỗi này và tạo một bộ sưu tập không an toàn. Tuy nhiên, nếu dán mã này vào IDEa, chúng ta sẽ thấy cảnh báo: “ Lệnh gọi add(E) as a member of raw type of java.util.List chưa được kiểm tra ”. Nó cho chúng ta biết rằng có thể đã xảy ra lỗi khi thêm một phần tử vào một bộ sưu tập không có thuốc generic không theo cách này. Nhưng cụm từ “loại thô” có nghĩa là gì? Bản dịch theo nghĩa đen sẽ khá chính xác - “ loại thô ” hoặc “ loại bẩn ”. Raw typelà một lớp chung mà loại của nó đã bị loại bỏ. Nói cách khác, List myList1đây là Raw type. Ngược lại raw typegeneric typemột lớp generic (còn được gọi là class parameterized type), được tạo chính xác, với đặc tả kiểu. Ví dụ, List<String> myList1. Bạn có thể có câu hỏi: tại sao nó lại được phép sử dụng raw types? Lý do rất đơn giản. Những người tạo ra Java đã để lại sự hỗ trợ bằng ngôn ngữ raw typesđể không tạo ra các vấn đề về khả năng tương thích. Vào thời điểm Java 5.0 được phát hành (các phiên bản generic xuất hiện lần đầu tiên trong phiên bản này), rất nhiều mã đã được viết bằng raw types. Vì vậy, khả năng này vẫn tồn tại 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 giảng. Là một trong những người sáng tạo ra ngôn ngữ, ông đã không bỏ qua chủ đề sử dụng raw typesvà trong cuốn sách generic types. Generics trong Java là gì - 2Chương 23 của cuốn sách này có một tiêu đề rất hùng hồn: “Đừng sử dụng kiểu 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 chúng generic typethành các lớp raw type.

Phương pháp gõ

Java cho phép bạn gõ các phương thức riêng lẻ, tạo ra cái gọi là các phương thức chung. Tại sao các phương pháp như vậy lại thuận tiện? Trước hết, vì chúng cho phép bạn làm việc với nhiều loại tham số 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 là một giải pháp tuyệt vời. Hãy xem một ví dụ. Giả sử chúng ta có một số loại danh sách myList1. Chúng tôi muốn xóa tất cả các giá trị khỏi nó và lấp đầy tất cả các khoảng trống bằng một giá trị mới. Lớp của chúng ta với một phương thức chung sẽ trông như thế này:
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("Старая строка 1");
       strings.add("Старая строка 2");
       strings.add("Старая строка 3");

       fill(strings, "Новая строка");

       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, nó có vẻ hơi khác thường:
public static <T> void fill(List<T> list, T val)
Kiểu trả về được bắt đầu bằng <T>, biểu thị một phương thức chung. Trong trường hợp này, phương thức lấy 2 tham số làm đầu vào: một 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>, việc gõ phương thức đã đạt được: chúng ta không thể chuyển danh sách các chuỗi và một số vào đó. Một danh sách các chuỗi và một chuỗi, một danh sách các số và một số, một danh sách các đối tượng của chúng ta Catvà một đối tượng khác Cat- đó là cách duy nhất. Phương pháp này main()chứng minh rõ ràng rằng phương pháp này fill()dễ dàng hoạt động với các loại dữ liệu khác nhau. Đầu tiên, nó lấy đầu vào là danh sách các chuỗi và một chuỗi, sau đó là danh sách các số và một số. Đầu ra của bảng điều khiển: [Newline, Newline, Newline] [888, 888, 888] Hãy tưởng tượng nếu fill()chúng ta cần logic phương thức cho 30 lớp khác nhau và chúng ta không có các phương thức chung. Chúng ta sẽ buộc phải viết cùng một phương thức 30 lần, chỉ dành 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! :)

Các lớp đánh máy

Bạn không chỉ có thể sử dụng các lớp chung được cung cấp trong Java mà còn có thể tạo lớp của riêng bạn! Đâ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("Старая строка");
       System.out.println(stringBox.get());
       stringBox.set("Новая строка");

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

       stringBox.set(12345);//ошибка компиляции!
   }
}
Lớp học của chúng tôi Box<T>(“hộp”) được gõ. Đã gán một kiểu dữ liệu ( ) cho nó trong quá trình tạo <T>, chúng ta sẽ không thể đặt các đối tượng thuộc loại khác vào đó nữa. Điều này có thể được nhìn thấy trong ví dụ. Khi tạo, chúng tôi đã chỉ định rằng đối tượng của chúng tôi sẽ hoạt động với chuỗi:
Box<String> stringBox = new Box<>();
Và khi ở dòng mã cuối cùng, chúng ta cố gắng đặt số 12345 vào trong hộp, chúng ta gặp lỗi biên dịch! Cứ như vậy, chúng ta đã tạo ra lớp chung của riêng mình! :) Điều này kết thúc bài giảng của chúng tôi ngày hôm nay. 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 giảng tiếp theo chúng ta sẽ nói về những tính năng nâng cao hơn, vì vậy đừng nói lời tạm biệt! ) Chúc may mắn trong các nghiên cứu của bạn! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION