CodeGym/Blog Java/Ngẫu nhiên/loại tẩy xóa

loại tẩy xóa

Xuất bản trong nhóm
CHÀO! Chúng tôi tiếp tục loạt bài học về thuốc generic. Trước đây chúng tôi đã có một ý tưởng chung về chúng là gì và tại sao chúng lại cần thiết. Hôm nay chúng ta sẽ tìm hiểu thêm về một số tính năng của thuốc generic và cách làm việc với chúng. Đi nào! Loại tẩy - 1Trong bài học trước , chúng ta đã nói về sự khác biệt giữa kiểu chungkiểu thô . Loại thô là một lớp chung có loại đã bị xóa.
List list = new ArrayList();
Đây là một ví dụ. Ở đây chúng tôi không chỉ ra loại đối tượng nào sẽ được đặt trong tệp List. Nếu chúng ta cố tạo một cái như vậy Listvà thêm một số đối tượng vào nó, chúng ta sẽ thấy một 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 ta cũng đã nói về thực tế là các generic chỉ xuất hiện trong Java 5. Vào thời điểm phiên bản này được phát hành, các lập trình viên đã viết một loạt mã bằng các kiểu thô, vì vậy tính năng này của ngôn ngữ không thể ngừng hoạt động và khả năng tạo các kiểu thô trong Java được giữ nguyên. Tuy nhiên, vấn đề hóa ra là phổ biến hơn. Như bạn đã biết, mã Java được chuyển đổi thành một định dạng được biên dịch đặc biệt gọi là mã byte, sau đó được thực thi bởi máy ảo Java. Nhưng nếu chúng ta đưa thông tin về tham số kiểu vào mã byte trong quá trình chuyển đổi, nó sẽ phá vỡ tất cả mã đã viết trước đó, vì không có tham số kiểu nào trước Java 5! Khi làm việc với thuốc generic, có một khái niệm rất quan trọng mà bạn cần nhớ. Nó được gọi là xóa kiểu. Nó có nghĩa là một lớp không chứa thông tin về một tham số kiểu. Thông tin này chỉ khả dụng trong quá trình biên dịch và bị xóa (không thể truy cập được) trước thời gian chạy. Nếu bạn cố gắng đặt sai loại đối tượng vào tệp của mình List<String>, trình biên dịch sẽ tạo ra lỗi. Đây chính xác là điều mà những người tạo ra ngôn ngữ muốn đạt được khi họ tạo ra các khái quát: kiểm tra thời gian biên dịch. Nhưng khi tất cả mã Java của bạn biến thành mã byte, nó không còn chứa thông tin về các tham số kiểu nữa. Trong mã byte, List<Cat>danh sách mèo của bạn không khác gì List<String>chuỗi. Trong mã byte, không có gì nói rằng đó catslà danh sách Catcác đối tượng. Những thông tin như vậy sẽ bị xóa trong quá trình biên dịch — chỉ thực tế là bạn có một List<Object> catsdanh sách sẽ kết thúc trong mã byte của chương trình. Hãy xem cách nó hoạt động:
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 lớp chung của riêng mình TestClass. Nó khá đơn giản: nó thực sự là một "bộ sưu tập" nhỏ gồm 2 đối tượng, được lưu trữ ngay khi đối tượng được tạo. Nó có 2 Tlĩnh vực. Khi createAndAdd2Values()phương thức được thực thi, hai đối tượng được truyền ( Object aObject bphải được truyền kiểu Trồi mới thêm vào TestClassđối tượng. Trong main()phương thức, chúng ta tạo a TestClass<Integer>, tức là Integerđối số kiểu thay thế tham Integersố kiểu. Chúng ta cũng đang truyền a Doublevà a Stringcho phương createAndAdd2Values()thức. Bạn có nghĩ rằng chương trình của chúng ta sẽ hoạt động không? Rốt cuộc, chúng ta đã chỉ định làm Integerđối số kiểu, nhưng a Stringchắc chắn không thể chuyển thành an Integer! Hãy chạy chương trìnhmain()phương pháp và kiểm tra. Đầu ra bảng điều khiển:
22.111
Test String
Đó là bất ngờ! Tại sao điều này xảy ra? Đó là kết quả của việc xóa kiểu. Thông tin về Integerđối số loại được sử dụng để khởi tạo TestClass<Integer> testđối tượng của chúng tôi đã bị xóa khi mã được biên dịch. Trường trở thành TestClass<Object> test. DoubleCác đối số and của chúng tôi Stringdễ dàng được chuyển đổi thành Objectđối tượng (chúng không được chuyển đổi thành Integerđối tượng như chúng tôi mong đợi!) và được thêm vào TestClass. Đây là một ví dụ đơn giản nhưng rất tiết lộ về kiểu xóa:
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 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 đối số khác nhau — String, và lớp Integerrất riêng của chúng tôi . CatNhưng trong quá trình chuyển đổi sang mã byte, cả ba danh sách đều trở thành List<Object>, vì vậy khi chương trình chạy, nó cho chúng ta biết rằng chúng ta đang sử dụng cùng một lớp trong cả ba trường hợp.

Gõ xóa khi làm việc với mảng và tổng quát

Có một điểm rất quan trọng phải được hiểu rõ ràng khi làm việc với mảng và các lớp chung (chẳng hạn như List). Bạn cũng nên cân nhắc khi chọn cấu trúc dữ liệu cho chương trình của mình. Thuốc generic có thể bị xóa loại. Thông tin về các tham số loại không có sẵn trong thời gian chạy. 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 khi chương trình đang chạy. Cố gắng đặt một loại không hợp lệ vào một mảng sẽ gây 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 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 như vậy giữa mảng và thuốc generic nên chúng có thể gặp vấn đề về tính tương thích. Trên 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ỉ một mảng được tham số hóa. Điều đó nghe có vẻ hơi khó hiểu phải không? Hãy xem qua. Ví dụ: bạn không thể thực hiện bất kỳ thao tác nào 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 List<String>đối tượng, chúng ta sẽ gặp lỗi biên dịch phàn nàn về việc tạo mảng chung:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       // Compilation error! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Nhưng tại sao điều này được thực hiện? Tại sao việc tạo các mảng như vậy không được phép? Đây là tất cả để cung cấp loại an toàn. Nếu trình biên dịch cho phép chúng ta tạo các mảng đối tượng chung như vậy, chúng ta có thể tự tạo ra vô số vấn đề cho chính mình. Đâ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ột mảng như List<String>[] stringListsđược cho phép và sẽ không tạo ra lỗi biên dịch. Nếu điều này là đúng, đây là một số điều chúng ta có thể làm: Trong dòng 1, chúng ta tạo một mảng gồm các danh sách: List<String>[] stringLists. Mảng của chúng tôi chứa một tệp List<String>. Ở dòng 2, chúng ta tạo một danh sách các số: List<Integer>. Ở dòng 3, chúng ta gán giá trị của chúng ta List<String>[]cho một Object[] objectsbiến. Ngôn ngữ Java cho phép điều này: một mảng Xcác đối tượng có thể lưu trữ Xcác đối tượng và các đối tượng của tất cả các lớp con X. Theo đó, bạn có thể đặt bất cứ thứ gì vào một Objectmảng. Ở dòng 4, chúng ta thay thế phần tử duy nhất của objects()mảng (a List<String>) bằng a List<Integer>. Vì vậy, chúng tôi đặt một List<Integer>trong một mảng chỉ nhằm mục đích lưu trữList<String>các đối tượng! Chúng tôi sẽ chỉ gặp lỗi khi chúng tôi thực hiện dòng 5. A ClassCastExceptionsẽ bị ném khi chạy. Theo đó, lệnh cấm tạo các mảng như vậy đã được thêm vào Java. Điều này cho phép chúng tôi tránh những tình huống như vậy.

Làm thế nào tôi có thể khắc phục kiểu xóa?

Chà, chúng tôi đã học về xóa kiểu. Hãy thử đánh lừa hệ thống! :) Nhiệm vụ: Chúng tôi có một TestClass<T>lớp chung. Chúng tôi muốn viết một createNewT()phương thức cho lớp này để tạo và trả về một Tđối tượng mới. Nhưng điều này là không thể, phải không? Tất cả thông tin về Tloại sẽ bị xóa trong quá trình biên dịch và trong thời gian chạy, chúng tôi không thể xác định loại đối tượng nào chúng tôi cần tạo. Có thực sự là một cách khó khăn để làm điều này. Bạn có thể nhớ rằng Java có một Classlớp. Chúng ta có thể sử dụng nó để xác định lớp của bất kỳ đối tượng nào của chúng ta:
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 bảng điều khiển:
class java.lang.Integer
class java.lang.String
Nhưng đây là một khía cạnh mà chúng ta chưa nói đến. Trong tài liệu của Oracle, bạn sẽ thấy rằng lớp Class là chung chung! Loại tẩy - 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

Tài liệu cho biết, "T - loại lớp được mô hình hóa bởi đối tượng Lớp này." Dịch điều này từ ngôn ngữ của tài liệu sang lời nói đơn giản, chúng tôi hiểu rằng lớp của Integer.classđối tượng không chỉ là Class, mà là Class<Integer>. Loại đối String.classtượng không chỉ là Class, mà là 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;
       // Compilation error!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       // Compilation error!
       Class<Double> classString2 = String.class;
   }
}
Và bây giờ, sử dụng kiến ​​thức này, chúng ta có thể bỏ qua việc xóa kiểu và hoàn thành nhiệm vụ của mình! Hãy thử lấy thông tin về một tham số kiểu. Đối số loại của chúng tôi sẽ là MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
Và đâ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 bảng điều khiển:
A MySecretClass object was created successfully!
Chúng tôi vừa chuyển đối số lớp bắt buộc tới hàm tạo của lớp chung:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Điều này cho phép chúng tôi lưu thông tin về đối số loại, ngăn không cho nó bị xóa hoàn toàn. Kết quả là, chúng tôi đã có thể tạo ra mộtTsự vật! :) Như vậy là bài học hôm nay đã kết thúc. Bạn phải luôn nhớ xóa kiểu khi làm việc với thuốc generic. Cách giải quyết này có vẻ không thuận tiện cho lắm, nhưng bạn nên hiểu rằng các khái quát không phải là một phần của ngôn ngữ Java khi nó được tạo. Tính năng này, giúp chúng tôi tạo các bộ sưu tập được tham số hóa và bắt lỗi trong quá trình biên dịch, đã được xử lý sau. Trong một số ngôn ngữ khác bao gồm các từ gốc từ phiên bản đầu tiên, không có kiểu xóa (ví dụ: trong C#). Nhân tiện, chúng ta chưa học xong thuốc generic! Trong bài học tiếp theo, bạn sẽ làm quen với một số tính năng khác của thuốc generic. Hiện tại, sẽ tốt hơn nếu giải quyết một vài nhiệm vụ! :)
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