JavaRush /Blog Java /Random-VI /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
Xin chào! Trong bài học hôm nay chúng ta sẽ tiếp tục nghiên cứu về generics. Tình cờ đây là một chủ đề lớn, nhưng không có nơi nào để đi - đây 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ề thuốc generic hoặc đọc hướng dẫn trên Internet, bạn sẽ bắt gặp các thuật ngữ Các loại không thể xác định lạicác loại có thể xác định lại . Từ “có thể xác nhận lại” là loại từ gì? Ngay cả khi mọi thứ đều tốt với tiếng Anh, bạn khó có thể gặp được nó. Hãy thử dịch nhé! Sử dụng varargs khi làm việc với generic - 2
*cảm ơn Google, bạn đã giúp đỡ rất nhiều -_-*
Loại có thể xác định lại là loại có thông tin đầy đủ trong thời gian chạy. Trong ngôn ngữ Java, chúng bao gồm các kiểu nguyên thủy, kiểu thô và kiểu không chung chung. Ngược lại, Loại không thể xác định lại là loại có thông tin bị xóa và không khả dụng khi chạy. Đây chỉ là những cái chung - 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à những đố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 chính xác 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 và nó có phương thức sum. sum()Bạn có thể truyền 2 số, 3, 5 hoặc bất kỳ số nào vào phương thức . Sẽ rất lạ nếu mỗi lần quá tải phương thức sum()để tính đến tất cả các tùy chọn 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 của bảng điều khiển:

15
11
Vì vậy, sử dụng varargskết hợp với thuốc generic có một số tính năng quan trọng. Hãy nhìn vào mã này:
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>(),  //  здесь все нормально
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // а здесь мы получаем предупреждение
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
Phương thức này lấy một danh sách và số lượng đối tượng bất kỳ addAll()làm đầu vào , sau đó thêm tất cả các đối tượng này vào danh sách. Trong phương thức này, chúng tôi gọi phương thức của mình hai lần . Lần đầu tiên chúng tôi thêm vào hai dòng thông thường. Mọi thứ ở đây đều ổn. Lần thứ hai chúng ta thêm vào hai đối tượng . Và ở đây chúng tôi đột nhiên nhận được một cảnh báo: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
Nó có nghĩa là gì? Tại sao chúng tôi nhận được cảnh báo và nó có liên quan gì đến nó array? Array- đây là một mảng và không có mảng nào trong mã của chúng tôi! Hãy bắt đầu với cái thứ hai. Cảnh báo đề cập đến một mảng vì trình biên dịch chuyển đổi các đối số có độ dài thay đổi (varargs) thành một mảng. Nói cách khác, chữ ký của phương pháp của chúng tôi là addAll():
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)
Tức là, trong phương thức main(), trình biên dịch sẽ chuyển đổi mã của chúng ta thành mã này:
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ọi thứ đều ổn với mảng String. Nhưng với một mảng Pair<String, String>- không. Thực tế Pair<String, String>đây là Loại không thể xác định lại được. Trong quá trình biên dịch, mọi thông tin về kiểu tham số (<String, String>) sẽ bị xóa. Không được phép tạo mảng từ Loại không thể xác định lại trong Java . Bạn có thể xác minh đ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) {

   //  ошибка компиляции! 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 nhớ, khi tạo một mảng, bạn phải chỉ ra những đối tượng (hoặc nguyên thủy) nào mà mảng này sẽ lưu trữ.
int array[] = new int[10];
Trong một trong những bài học trước, chúng ta đã xem xét chi tiết cơ chế xóa kiểu. Vì vậy, trong trường hợp này, do xóa các loại, chúng tôi đã mất thông tin về Paircác cặp được lưu trữ trong đối tượng của chúng tôi <String, String>. Tạo một mảng sẽ không an toàn. Khi sử dụng các phương thức with varargsvà generics, hãy nhớ nhớ về cách xóa kiểu và cách thức hoạt động chính xác của nó. Nếu bạn hoàn toàn tin tưởng vào mã mình đã viết và bạn biết rằng nó sẽ không gây ra bất kỳ vấn đề nào, bạn có thể tắt varargscác cảnh báo liên quan đến mã đó bằng cách sử dụng chú thích@SafeVarargs
@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 phải trước đó sẽ không xuất hiện. Một vấn đề khác có thể xảy ra khi sử dụng varargsthuốc generic cùng nhau là ô nhiễm đống. Sử dụng varargs khi làm việc với generic - 4Sự ô nhiễm có thể xảy ra trong các trường hợp sau:
import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> makeHeapPollution() {
       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 = makeHeapPollution();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
Đầu ra của 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à tình huống trong đó các đối tượng loại 1 đáng lẽ phải ở trên đống А, nhưng các đối tượng thuộc loại lại ở đó Bdo lỗi an toàn loại. Trong ví dụ của chúng tôi đây là những gì xảy ra. Đầu tiên chúng tôi tạo một biến Raw numbersvà gán cho nó một bộ sưu tập chung ArrayList<Number>. Sau đó chúng tôi đã thêm số vào đó 1.
List<String> strings = numbers;
Trong 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 “ Nhiệm vụ chưa được kiểm tra... ”, nhưng chúng tôi đã bỏ qua nó. Kết quả là, chúng ta có một biến chung loại type List<String>, biến này trỏ đến một tập hợp chung loại type ArrayList<Number>. Tình huống này rõ ràng có thể dẫn đến rắc rối! Đây là những gì sẽ xảy ra. Sử dụng biến mới, chúng tôi thêm một chuỗi vào bộ sưu tập. Vùng heap bị ô nhiễm - trước tiên chúng tôi đã thêm một số và sau đó là một chuỗi vào bộ sưu tập đã nhập. Trình biên dịch đã cảnh báo chúng tôi, nhưng chúng tôi đã bỏ qua cảnh báo của nó, ClassCastExceptionchỉ nhận được kết quả khi chương trình đang chạy. Nó có liên quan gì với nó varargs? Sử dụng varargschung 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 makeHeapPollution(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");

       makeHeapPollution(cars1, cars2);
   }
}
Những gì đang xảy ra ở đây? Do bị xóa kiểu, các bảng tham số của chúng tôi (chúng tôi sẽ gọi chúng là “trang tính” thay vì “danh sách” để thuận tiện) là -
List<String>...stringsLists
- sẽ biến thành một mảng các trang tính - List[]với loại không xác định (đừng quên rằng các biến thể biến thành một mảng thông thường do quá trình biên dịch). Do đó, chúng ta có thể dễ dàng gán cho một biến Object[] arrayở dòng đầu tiên của phương thức - các kiểu đó đã bị xóa khỏi trang tính của chúng ta! Và bây giờ chúng ta có một biến kiểu Object[], nơi chúng ta có thể thêm bất cứ thứ gì - tất cả các đối tượng trong Java đều kế thừa từ Object! Hiện tại chúng tôi chỉ có một mảng các tấm chuỗi. Nhưng nhờ sử dụng varargsvà xóa các kiểu, chúng ta có thể dễ dàng thêm một bảng số vào chúng, đó là những gì chúng ta làm. Kết quả là chúng ta làm ô nhiễm heap bằng cách trộn lẫn các đối tượng thuộc nhiều loại khác nhau. Kết quả sẽ có ngoại lệ tương tự ClassCastExceptionkhi cố đọc một chuỗi từ mảng. Đầu ra của bảng điều khiển:

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