JavaRush /Blog Java /Random-VI /Các loại xóa

Các loại xóa

Xuất bản trong nhóm
Xin chào! Chúng tôi tiếp tục loạt bài giảng về thuốc generic. Trước đây , chúng tôi đã tìm ra một cách khái quát nó là gì và tại sao nó lại cần thiết. Hôm nay chúng ta sẽ nói về một số tính năng của thuốc generic và xem xét một số cạm bẫy khi làm việc với chúng. Đi! Các loại xóa - 1Trong bài giảng trước, chúng ta đã nói về sự khác biệt giữa Loại chungLoại thô . Trong trường hợp bạn quên, Loại thô là một lớp chung mà loại của nó đã bị xóa.
List list = new ArrayList();
Đây là một ví dụ. Ở đây chúng tôi không chỉ định loại đối tượng nào sẽ được đặt trong tệp List. Nếu chúng ta cố gắng tạo một cái Listvà thêm một số đối tượng vào nó, chúng ta sẽ thấy cảnh báo trong IDEa:

“Unchecked call to add(E) as a member of raw type of java.util.List”.
Nhưng chúng tôi cũng nói về thực tế là generics chỉ xuất hiện trong phiên bản ngôn ngữ Java 5. Vào thời điểm nó được phát hành, các lập trình viên đã viết rất nhiều mã bằng cách sử dụng Loại thô và để nó không ngừng hoạt động, khả năng việc tạo và làm việc với Kiểu thô trong Java vẫn được giữ nguyên. Tuy nhiên, vấn đề này hóa ra lại rộng hơn nhiều. Mã Java, như bạn biết, được chuyển đổi thành mã byte đặc biệt, sau đó được thực thi bởi máy ảo Java. Và nếu trong quá trình dịch mà chúng ta đặt thông tin về các loại tham số vào bytecode thì nó sẽ phá vỡ tất cả các mã đã viết trước đó, vì trước Java 5 không tồn tại các loại tham số! Khi làm việc với thuốc generic, có một tính năng rất quan trọng mà bạn cần nhớ. Nó được gọi là loại tẩy xóa. Bản chất của nó nằm ở chỗ không có thông tin nào về loại tham số của nó được lưu trữ bên trong lớp. Thông tin này chỉ có ở giai đoạn biên dịch và bị xóa (không thể truy cập được) khi chạy. Nếu bạn cố gắng đặt một đối tượng không đúng loại vào List<String>, trình biên dịch sẽ báo lỗi. Đây chính xác là những gì những người tạo ra ngôn ngữ đạt được bằng cách tạo ra các khái quát - kiểm tra ở giai đoạn biên dịch. Nhưng khi tất cả mã Java bạn viết chuyển thành mã byte, sẽ không có thông tin về các loại tham số. Bên trong mã byte, danh sách List<Cat>mèo của bạn sẽ không khác với List<String>chuỗi. Không có gì trong mã byte sẽ nói rằng catsđây là danh sách các đối tượng Cat. Thông tin về điều này sẽ bị xóa trong quá trình biên dịch và chỉ thông tin mà bạn có một danh sách nhất định trong chương trình của mình mới được đưa vào mã byte List<Object> cats. Hãy xem nó hoạt động như thế nào:
public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
Chúng tôi đã tạo ra lớp chung của riêng mình TestClass. Nó khá đơn giản: về cơ bản nó là một “bộ sưu tập” nhỏ gồm 2 đối tượng, được đặt ở đó ngay lập tức khi đối tượng được tạo. Nó có 2 đối tượng là trường T. Khi phương thức được thực thi, createAndAdd2Values()hai đối tượng đã truyền sẽ được chuyển Object athành Object bkiểu của chúng ta T, sau đó chúng sẽ được thêm vào đối tượng TestClass. Trong phương pháp main()chúng ta tạo ra TestClass<Integer>, tức là về chất lượng Tchúng ta sẽ có Integer. Nhưng đồng thời, createAndAdd2Values()chúng ta truyền một số Doublevà một đối tượng vào phương thức String. Bạn có nghĩ rằng chương trình của chúng tôi sẽ hoạt động? Rốt cuộc, chúng tôi đã chỉ định làm loại tham số Integer, nhưng Stringchắc chắn nó không thể được chuyển thành Integer! Hãy chạy phương thức main()và kiểm tra. Đầu ra của bảng điều khiển: 22.111 Chuỗi thử nghiệm Kết quả không mong đợi! Tại sao điều này xảy ra? Chính xác là do kiểu xóa. Trong quá trình biên dịch mã, thông tin về loại tham số Integercủa đối tượng của chúng tôi TestClass<Integer> testđã bị xóa. Anh ta biến thành TestClass<Object> test. Các tham số của chúng tôi đã được chuyển đổi thành mà không gặp bất kỳ sự cố nào Double( và không thành , như chúng tôi mong đợi!) và được thêm vào . Đây là một ví dụ đơn giản nhưng rất minh họa khác về việc xóa kiểu: StringObjectIntegerTestClass
import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
Đầu ra của bảng điều khiển: true true Có vẻ như chúng tôi đã tạo các bộ sưu tập với ba loại tham số khác nhau - String, Integer, và lớp mà chúng tôi đã tạo Cat. Nhưng trong quá trình chuyển đổi sang mã byte, cả ba danh sách đều chuyển thành List<Object>, vì vậy khi thực thi, chương trình sẽ cho chúng ta biết rằng trong cả ba trường hợp, chúng ta đều sử dụng cùng một lớp.

Gõ tẩy xóa khi làm việc với mảng và generics

Có một điểm rất quan trọng cần phải hiểu rõ ràng khi làm việc với mảng và tổng quát (ví dụ: List). Nó cũng đáng được cân nhắc khi chọn cấu trúc dữ liệu cho chương trình của bạn. Generics có thể bị xóa. Thông tin về loại tham số không có sẵn trong quá trình thực hiện chương trình. Ngược lại, mảng biết và có thể sử dụng thông tin về kiểu dữ liệu của chúng trong quá trình thực thi chương trình. Cố gắng đặt một giá trị sai kiểu vào một mảng sẽ tạo ra một ngoại lệ:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Đầu ra của bảng điều khiển:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Vì có sự khác biệt lớn giữa mảng và generic nên chúng có thể có vấn đề về tương thích. Trước hết, bạn không thể tạo một mảng các đối tượng chung chung hoặc thậm chí chỉ là một mảng được gõ. Nghe có vẻ hơi khó hiểu? Chúng ta hãy xem xét kỹ hơn. Ví dụ: bạn không thể thực hiện bất kỳ thao tác nào trong số này trong Java:
new List<T>[]
new List<String>[]
new T[]
Nếu chúng ta cố gắng tạo một mảng các danh sách List<String>, chúng ta sẽ gặp lỗi biên dịch tạo mảng chung:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Nhưng tại sao điều này lại được thực hiện? Tại sao việc tạo các mảng như vậy lại bị cấm? Đây là tất cả để đảm bảo an toàn loại. Nếu trình biên dịch cho phép chúng ta tạo các mảng như vậy từ các đối tượng chung, chúng ta có thể gặp rất nhiều rắc rối. Đây là một ví dụ đơn giản từ cuốn sách “Java hiệu quả” của Joshua Bloch:
public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
Hãy tưởng tượng rằng việc tạo mảng List<String>[] stringListssẽ được cho phép và trình biên dịch sẽ không phàn nàn. Đây là những gì chúng ta có thể làm trong trường hợp này: Ở dòng 1, chúng ta tạo một mảng các trang tính List<String>[] stringLists. Mảng của chúng tôi chứa một List<String>. Trên dòng 2, chúng tôi tạo một danh sách các số List<Integer>. Trên dòng 3, chúng tôi gán mảng của mình List<String>[]cho một biến Object[] objects. Ngôn ngữ Java cho phép bạn thực hiện điều này: Xbạn có thể đặt cả đối tượng Xvà đối tượng của tất cả các lớp con vào một mảng đối tượng Х. Theo đó, Objectsbạn có thể đặt bất cứ thứ gì vào mảng. Ở dòng 4, chúng ta thay thế phần tử duy nhất của mảng objects (List<String>)bằng một danh sách List<Integer>. Kết quả là, chúng tôi đã đặt List<Integer>vào mảng của mình, mảng này chỉ nhằm mục đích lưu trữ List<String>! Chúng ta sẽ chỉ gặp lỗi khi mã đạt đến dòng 5. Một ngoại lệ sẽ được đưa ra trong quá trình thực thi chương trình ClassCastException. Do đó, lệnh cấm tạo các mảng như vậy đã được đưa vào ngôn ngữ Java - điều này cho phép chúng ta tránh những tình huống như vậy.

Làm cách nào tôi có thể bỏ qua việc xóa kiểu?

Vâng, chúng ta đã học về cách xóa kiểu. Hãy thử gian lận hệ thống! :) Nhiệm vụ: Chúng tôi có một lớp chung TestClass<T>. Chúng ta cần tạo một phương thức trong đó createNewT()để tạo và trả về một đối tượng mới thuộc loại Т. Nhưng điều này là không thể thực hiện được, phải không? Tất cả thông tin về loại Тsẽ bị xóa trong quá trình biên dịch và trong khi chương trình đang chạy, chúng ta sẽ không thể tìm ra loại đối tượng mình cần tạo. Trên thực tế, có một cách phức tạp. Bạn có thể nhớ rằng có một lớp trong Java Class. Sử dụng nó, chúng ta có thể lấy được lớp của bất kỳ đối tượng nào:
public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
Đầu ra của bảng điều khiển:

class java.lang.Integer
class java.lang.String
Nhưng đây là một tính năng mà chúng tôi chưa nói đến. Trong tài liệu của Oracle bạn sẽ thấy Class là một lớp chung! Các kiểu xóa - 3Tài liệu cho biết: “T là loại lớp được mô hình hóa bởi đối tượng Class này.” Nếu chúng ta dịch điều này từ ngôn ngữ tài liệu sang ngôn ngữ của con người, điều này có nghĩa là lớp dành cho một đối tượng Integer.classkhông chỉ là Class, mà là Class<Integer>. Loại của một đối tượng string.classkhông chỉ là Class, Class<String>, v.v. Nếu vẫn chưa rõ, hãy thử thêm tham số loại vào ví dụ trước:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
Và bây giờ, bằng cách sử dụng kiến ​​thức này, chúng ta có thể bỏ qua việc xóa kiểu và giải quyết vấn đề của mình! Hãy thử lấy thông tin về loại tham số. Vai trò của nó sẽ được thực hiện bởi lớp MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
Đây là cách chúng tôi sử dụng giải pháp của mình trong thực tế:
public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
Đầu ra của bảng điều khiển:

Объект секретного класса успешно создан!
Chúng ta chỉ đơn giản truyền tham số lớp được yêu cầu cho hàm tạo của lớp chung:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Nhờ đó, chúng tôi đã lưu được thông tin về loại tham số và bảo vệ nó khỏi bị xóa. Kết quả là chúng ta đã có thể tạo ra một đối tượng T! :) Điều này kết thúc bài giảng ngày hôm nay. Xóa kiểu luôn là điều cần lưu ý khi làm việc với thuốc generic. Điều này có vẻ không thuận tiện lắm, nhưng bạn cần hiểu rằng generics không phải là một phần của ngôn ngữ Java khi nó được tạo ra. Đây là tính năng được bổ sung sau này giúp chúng ta tạo các bộ sưu tập được đánh máy và phát hiện lỗi ở giai đoạn biên dịch. Một số ngôn ngữ khác có generics kể từ phiên bản 1 không có tính năng xóa kiểu (ví dụ: C#). Tuy nhiên, chúng tôi chưa hoàn thành việc nghiên cứu thuốc generic! Trong bài giảng tiếp theo, bạn sẽ làm quen với một số tính năng khác khi làm việc với chúng. Trong lúc đó, sẽ tốt hơn nếu giải quyết được một số vấn đề! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION