CodeGym /Blog Java /Ngẫu nhiên /Sử dụng varargs khi làm việc với thuốc generic

Sử dụng varargs khi làm việc với thuốc generic

Xuất bản trong nhóm
CHÀO! Trong bài học hôm nay, chúng ta sẽ tiếp tục nghiên cứu về thuốc generic. Khi nó xảy ra, đây là một chủ đề lớn, nhưng không thể tránh khỏi nó - đó là một phần cực kỳ quan trọng của ngôn ngữ :) Khi bạn nghiên cứu tài liệu của Oracle về khái quát hoặc đọc các hướng dẫn trực tuyến, bạn sẽ bắt gặp các thuật ngữ loại không thể thay đổi đượccác loại reifable . Một loại reifiable là một loại thông tin có sẵn đầy đủ trong thời gian chạy. Trong Java, các kiểu như vậy bao gồm kiểu nguyên thủy, kiểu thô và kiểu không chung chung. Ngược lại, các loại không thể thay đổi được là các loại có thông tin bị xóa và không thể truy cập được trong thời gian chạy. Khi nó xảy ra, đây là những thuốc generic — List<String>, List<Integer>, v.v.

Nhân tiện, bạn có nhớ varargs là gì không?

Trong trường hợp bạn quên, đây là đối số có độ dài thay đổi. Chúng rất hữu ích trong những tình huống mà chúng ta không biết có bao nhiêu đối số có thể được truyền cho phương thức của mình. Ví dụ: nếu chúng ta có một lớp máy tính có một sumphương thức. Phương sum()pháp có thể nhận 2 số, hoặc 3, hoặc 5 hoặc bao nhiêu tùy thích. Sẽ rất lạ khi làm quá tải sum()phương thức cho mọi số lượng đối số có thể. Thay vào đó, chúng ta có thể làm điều này:

public class SimpleCalculator {

   public static int sum(int...numbers) {

       int result = 0;

       for(int i : numbers) {

           result += i;
       }

       return result;
   }

   public static void main(String[] args) {

       System.out.println(sum(1,2,3,4,5));
       System.out.println(sum(2,9));
   }
}
Đầu ra bảng điều khiển:

15
11
Điều này cho chúng ta thấy rằng có một số tính năng quan trọng khi sử dụng varargs kết hợp với thuốc generic. Hãy xem đoạn mã sau:

import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static <E> void addAll(List<E> list, E... array) {

       for (E element : array) {
           list.add(element);
       }
   }

   public static void main(String[] args) {
       addAll(new ArrayList<String>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
Phương addAll()thức lấy đầu vào là a List<E>và bất kỳ số lượng Eđối tượng nào, sau đó nó thêm tất cả các đối tượng này vào danh sách. Trong main()phương thức, chúng ta gọi addAll()phương thức của mình hai lần. Trong trường hợp đầu tiên, chúng tôi thêm hai chuỗi thông thường vào tệp List. Mọi thứ đều theo thứ tự ở đây. Trong trường hợp thứ hai, chúng tôi thêm hai Pair<String, String>đối tượng vào tệp List. Nhưng ở đây chúng tôi bất ngờ nhận được một cảnh báo:

Unchecked generics array creation for varargs parameter
Điều đó nghĩa là gì? Tại sao chúng tôi nhận được cảnh báo và tại sao lại có bất kỳ đề cập nào về an array? Xét cho cùng, mã của chúng tôi không có array! Hãy bắt đầu với trường hợp thứ hai. Cảnh báo đề cập đến một mảng vì trình biên dịch chuyển đổi đối số độ dài thay đổi (varargs) thành một mảng. Nói cách khác, chữ ký của addAll()phương pháp của chúng tôi là:

public static <E> void addAll(List<E> list, E... array)
Nó thực sự trông như thế này:

public static <E> void addAll(List<E> list, E[] array)
Đó là, trong main()phương thức, trình biên dịch chuyển đổi mã của chúng ta thành:

public static void main(String[] args) { 
   addAll(new ArrayList<String>(), 
      new String[] { 
        "Leonardo da Vinci", 
        "Vasco de Gama" 
      } 
   ); 
   addAll(new ArrayList<Pair<String,String>>(),
        new Pair<String,String>[] { 
            new Pair<String,String>("Leonardo","da Vinci"), 
            new Pair<String,String>("Vasco","de Gama") 
        } 
   ); 
}
Một Stringmảng là tốt. Nhưng một Pair<String, String>mảng thì không. Vấn đề là đó Pair<String, String>là một loại không thể thay đổi được. Trong quá trình biên dịch, tất cả thông tin về đối số kiểu (<Chuỗi, Chuỗi>) sẽ bị xóa. Việc tạo các mảng thuộc loại không thể thay đổi được không được phép trong Java . Bạn có thể thấy điều này nếu bạn cố gắng tạo một mảng Pair<String, String> theo cách thủ công

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Lý do rất rõ ràng: loại an toàn. Như bạn sẽ nhớ lại, khi tạo một mảng, bạn chắc chắn cần chỉ định những đối tượng (hoặc nguyên hàm) mà mảng sẽ lưu trữ.

int array[] = new int[10];
Trong một trong những bài học trước của chúng tôi, chúng tôi đã kiểm tra chi tiết về xóa kiểu. Trong trường hợp này, xóa kiểu khiến chúng ta mất thông tin mà các Pairđối tượng lưu trữ <String, String>cặp. Tạo mảng sẽ không an toàn. Khi sử dụng các phương pháp liên quan đến vararg và generic, hãy nhớ về cách xóa kiểu và cách thức hoạt động của nó. Nếu bạn hoàn toàn chắc chắn về mã bạn đã viết và biết rằng mã đó sẽ không gây ra bất kỳ sự cố nào, bạn có thể tắt các cảnh báo liên quan đến varargs bằng cách sử dụng các @SafeVarargschú thích.

@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
Nếu bạn thêm chú thích này vào phương pháp của mình, cảnh báo mà chúng tôi đã gặp trước đó sẽ không xuất hiện. Một vấn đề khác có thể xảy ra khi sử dụng varargs với thuốc generic là ô nhiễm đống. Sử dụng varargs khi làm việc với thuốc generic - 3Ô nhiễm đống có thể xảy ra trong tình huống sau:

import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> polluteHeap() {
       List numbers = new ArrayList<Number>();
       numbers.add(1);
       List<String> strings = numbers;
       strings.add("");
       return strings;
   }

   public static void main(String[] args) {

       List<String> stringsWithHeapPollution = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
Đầu ra bảng điều khiển:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Nói một cách đơn giản, ô nhiễm đống là khi các đối tượng thuộc loại Anên ở trong đống, nhưng các đối tượng thuộc loại Blại kết thúc ở đó do lỗi liên quan đến an toàn loại. Trong ví dụ của chúng tôi, đây chính xác là những gì xảy ra. Đầu tiên, chúng tôi đã tạo biến raw numbersvà gán một bộ sưu tập chung ( ArrayList<Number>) cho nó. Sau đó, chúng tôi đã thêm số 1vào bộ sưu tập.

List<String> strings = numbers;
Trên dòng này, trình biên dịch đã cố gắng cảnh báo chúng tôi về các lỗi có thể xảy ra bằng cách đưa ra cảnh báo " Unchecked assignment... ", nhưng chúng tôi đã bỏ qua nó. Chúng tôi kết thúc với một biến chung của loại List<String>trỏ đến một bộ sưu tập chung của loại ArrayList<Number>. Rõ ràng, tình huống này có thể dẫn đến rắc rối! Và như vậy nó làm. Sử dụng biến mới của chúng tôi, chúng tôi thêm một chuỗi vào bộ sưu tập. Bây giờ chúng tôi có ô nhiễm đống - chúng tôi đã thêm một số và sau đó là một chuỗi vào bộ sưu tập được tham số hóa. Trình biên dịch đã cảnh báo chúng tôi, nhưng chúng tôi đã phớt lờ cảnh báo của nó. Kết quả là, chúng tôi ClassCastExceptionchỉ nhận được khi chương trình đang chạy. Vậy điều này có liên quan gì đến varargs? Sử dụng varargs với thuốc generic có thể dễ dàng dẫn đến ô nhiễm đống. Đây là một ví dụ đơn giản:

import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(List<String>... stringsLists) {
       Object[] array = stringsLists;
       List<Integer> numbersList = Arrays.asList(66,22,44,12);

       array[0] = numbersList;
       String str = stringsLists[0].get(0);
   }

   public static void main(String[] args) {

       List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
       List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");

       polluteHeap(cars1, cars2);
   }
}
Những gì đang xảy ra ở đây? Do xóa kiểu, đối số có độ dài thay đổi của chúng tôi

List<String>...stringsLists
trở thành một mảng danh sách, nghĩa là List[], của các đối tượng thuộc loại không xác định (đừng quên rằng varargs biến thành một mảng thông thường trong quá trình biên dịch). Do đó, chúng ta có thể dễ dàng gán nó cho Object[] arraybiến ở dòng đầu tiên của phương thức — loại đối tượng trong danh sách của chúng ta đã bị xóa! Và bây giờ chúng ta có một Object[]biến mà chúng ta có thể thêm bất cứ thứ gì vào đó, vì tất cả các đối tượng trong Java đều kế thừa Object! Đầu tiên, chúng ta chỉ có một mảng danh sách các chuỗi. Nhưng nhờ xóa kiểu và sử dụng varargs, chúng tôi có thể dễ dàng thêm danh sách các số mà chúng tôi thực hiện. Kết quả là, chúng tôi làm ô nhiễm đống bằng cách trộn các đối tượng thuộc các loại khác nhau. Kết quả sẽ khác ClassCastExceptionkhi chúng ta cố đọc một chuỗi từ mảng. Đầu ra bảng điều khiển:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Việc sử dụng varargs, một cơ chế tưởng chừng như đơn giản có thể gây ra những hậu quả không mong muốn như vậy :) Và như vậy, bài học hôm nay đến đây là hết. Đừng quên giải quyết một vài nhiệm vụ và nếu bạn có thời gian và sức lực, hãy nghiên cứu thêm một số bài đọc. " Java hiệu quả " sẽ không tự đọc! :) Cho đến lần sau!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION