JavaRush /Java Blog /Random-TW /使用泛型時使用可變參數

使用泛型時使用可變參數

在 Random-TW 群組發布
你好!在今天的課程中,我們將繼續學習泛型。碰巧這是一個很大的主題,但無處可去 - 這是該語言極其重要的部分:) 當您研究有關泛型的 Oracle 文件或閱讀 Internet 上的指南時,您會遇到這些術語不可具體化類型可具體化類型。「可具體化」是個什麼樣的字?即使英語一切都很好,你也不太可能遇到它。我們來試試翻譯一下吧! 使用泛型時使用可變參數 - 2
*謝謝谷歌,你幫了很多忙-_-*
可具體化類型是一種其資訊在運行時完全可用的類型。在 Java 語言中,這些型別包括基元、原始型別和非泛型型別。相反,不可具體化類型是其資訊在運行時被刪除且不可用的類型。這些只是泛型 - List<String>List<Integer>等。

順便問一下,您還記得可變參數是什麼嗎?

如果您忘記了,這些是可變長度參數。當我們不知道到底有多少參數可以傳遞給我們的方法時,它們就會派上用場。例如,如果我們有一個計算器類別並且它有一個方法sumsum()您可以將 2 個數字、3 個、5 個或任意數量的數字傳遞給該方法。sum()每次都重載該方法以考慮所有可能的選項,這會很奇怪。相反,我們可以這樣做:
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));
   }
}
控制台輸出:

15
11
因此,當varargs與泛型結合使用時,有一些重要的特徵。我們來看看這段程式碼:
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")
       );
   }
}
此方法採用一個列表和任意數量的物件addAll()作為輸入,然後將所有這些物件加入列表中。在該方法中,我們調用我們的方法兩次。第一次我們新增了兩條常規行。這裡一切都很好。第二次我們增加兩個物件。在這裡我們突然收到一個警告: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
這是什麼意思?為什麼我們會收到警告?這與警告有何關係arrayArray- 這是一個數組,而我們的程式碼中沒有數組!讓我們從第二個開始。該警告提到了數組,因為編譯器將可變長度參數 (varargs) 轉換為數組。換句話說,我們方法的簽名是addAll()
public static <E> void addAll(List<E> list, E... array)
它實際上看起來像這樣:
public static <E> void addAll(List<E> list, E[] array)
也就是說,在方法中main(),編譯器會將我們的程式碼轉換為:
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")
        }
   );
}
陣列一切都很好String。但對於數組Pair<String, String>- 不。事實上,Pair<String, String>這是一種不可具體化的類型。編譯期間,所有有關參數類型(<String, String>)的資訊都將被刪除。 Java 中不允許從不可具體化類型建立數組。如果您嘗試手動建立陣列 Pair<String, String>,您可以驗證這一點
public static void main(String[] args) {

   //  ошибка компиляции! Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
原因很明顯——類型安全。您還記得,在建立陣列時,必須指示該陣列將儲存哪些物件(或基元)。
int array[] = new int[10];
在之前的一課中,我們詳細研究了類型擦除機制。因此,在這種情況下,由於擦除類型,我們丟失了Pair儲存在物件中的資訊對<String, String>。創建數組是不安全的。將方法與varargs泛型一起使用時,請務必記住類型擦除及其工作原理。如果您對所編寫的程式碼絕對有信心,並且知道它不會導致任何問題,則可以varargs使用註釋來停用與其關聯的警告@SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
如果您將此註釋新增到您的方法中,我們先前遇到的警告將不會出現。一起使用泛型時另一個可能的問題varargs是堆污染。 使用泛型時使用可變參數 - 4在以下情況下可能會發生污染:
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));
   }
}
控制台輸出:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
簡單來說,堆污染是指類型 1 的物件應該位於堆上,但由於類型安全錯誤,А類型 1 的物件最終出現在堆上的情況。B在我們的範例中,這就是發生的情況。首先,我們建立了一個 Raw 變數numbers並為其分配了一個通用集合ArrayList<Number>。之後我們在那裡添加了號碼1
List<String> strings = numbers;
在這一行中,編譯器試圖透過發出警告「Unchecked assignment...」來警告我們可能出現的錯誤,但我們忽略了它。結果,我們有一個類型為 的泛型變量List<String>,它指向類型為 的泛型集合ArrayList<Number>。這種情況顯然會帶來麻煩!這就是發生的事情。使用新變量,我們將一個字串新增到集合中。堆被污染了 - 我們首先在類型化集合中添加了一個數字,然後添加了一個字串。編譯器向我們發出警告,但我們忽略了它的警告,ClassCastException僅在程式運行時接收結果。跟它有什麼關係呢varargs與泛型一起使用varargs很容易導致堆污染。 這是一個簡單的例子:
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);
   }
}
這裡發生了什麼事?由於類型擦除,我們的參數表(為了方便起見,我們將其稱為“表”而不是“列表”)是 -
List<String>...stringsLists
- 將變成一個工作表數組 -List[]具有未知類型(不要忘記 varargs 由於編譯而變成常規數組)。因此,我們可以輕鬆地Object[] array在方法的第一行中對變數進行賦值 - 類型已從我們的工作表中刪除!現在我們有了一個類型變量Object[],我們可以在其中添加任何內容 - Java 中的所有物件都繼承自Object!現在我們只有一個字串表數組。但由於varargs類型的使用和刪除,我們可以輕鬆地向它們添加一張數字,這就是我們所做的。結果,我們透過混合不同類型的物件來污染堆。ClassCastException當嘗試從數組中讀取字串時,結果將是相同的異常。控制台輸出:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
這些是使用看似簡單的機制可能導致的意想不到的後果varargs:)我們今天的講座就到此結束。不要忘記解決幾個問題,如果您還有時間和精力,請研究更多文獻。「Effective Java」不會自動讀取!:) 再見!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION