CodeGym /Blog Java /rawak /Menggunakan varargs apabila bekerja dengan generik
John Squirrels
Tahap
San Francisco

Menggunakan varargs apabila bekerja dengan generik

Diterbitkan dalam kumpulan
Hai! Dalam pelajaran hari ini, kita akan terus mempelajari generik. Seperti yang berlaku, ini adalah topik yang besar, tetapi ia tidak boleh dielakkan — ia merupakan bahagian bahasa yang sangat penting :) Apabila anda mengkaji dokumentasi Oracle mengenai generik atau membaca tutorial dalam talian, anda akan menemui istilah jenis yang tidak boleh diperbaharui dan jenis reifiable . Jenis reifiable ialah jenis yang maklumatnya tersedia sepenuhnya pada masa jalan. Di Jawa, jenis tersebut termasuk jenis primitif, jenis mentah dan jenis bukan generik. Sebaliknya, jenis tidak boleh diperbaharui ialah jenis yang maklumatnya dipadamkan dan menjadi tidak boleh diakses pada masa jalan. Seperti yang berlaku, ini adalah generik — List<String>, List<Integer>, dsb.

By the way, adakah anda masih ingat apa itu varargs?

Sekiranya anda terlupa, ini adalah hujah panjang boleh ubah. Ia berguna dalam situasi di mana kita tidak tahu berapa banyak hujah yang mungkin dihantar kepada kaedah kita. Sebagai contoh, jika kita mempunyai kelas kalkulator yang mempunyai sumkaedah. Kaedah ini sum()boleh menerima 2 nombor, atau 3, atau 5, atau seberapa banyak yang anda suka. Ia akan menjadi sangat pelik untuk membebankan sum()kaedah untuk setiap bilangan argumen yang mungkin. Sebaliknya, kita boleh melakukan ini:

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));
   }
}
Output konsol:

15
11
Ini menunjukkan kepada kita bahawa terdapat beberapa ciri penting apabila menggunakan varargs dalam kombinasi dengan generik. Mari lihat kod berikut:

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")
       );
   }
}
Kaedah addAll()mengambil sebagai input a List<E>dan sebarang bilangan Eobjek, dan kemudian ia menambah semua objek ini ke senarai. Dalam main()kaedah, kami memanggil addAll()kaedah kami dua kali. Dalam kes pertama, kami menambah dua rentetan biasa pada List. Semuanya teratur di sini. Dalam kes kedua, kami menambah dua Pair<String, String>objek pada List. Tetapi di sini kami secara tidak dijangka menerima amaran:

Unchecked generics array creation for varargs parameter
Apakah maksudnya? Mengapa kita mendapat amaran dan mengapa terdapat sebarang sebutan tentang array? Lagipun, kod kami tidak mempunyai array! Mari kita mulakan dengan kes kedua. Amaran menyebut tatasusunan kerana pengkompil menukar hujah panjang pembolehubah (varargs) kepada tatasusunan. Dengan kata lain, tandatangan addAll()kaedah kami ialah:

public static <E> void addAll(List<E> list, E... array)
Ia sebenarnya kelihatan seperti ini:

public static <E> void addAll(List<E> list, E[] array)
Iaitu, dalam main()kaedah, pengkompil menukar kod kami kepada ini:

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") 
        } 
   ); 
}
Tatasusunan Stringadalah baik. Tetapi Pair<String, String>array tidak. Masalahnya ialah Pair<String, String>jenis yang tidak boleh diperbaharui. Semasa penyusunan, semua maklumat tentang jenis argumen (<String, String>) dipadamkan. Mencipta tatasusunan jenis yang tidak boleh diperbaharui tidak dibenarkan dalam Java . Anda boleh melihat ini jika anda cuba mencipta tatasusunan Pair<String, String> secara manual

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Sebabnya jelas: jenis keselamatan. Seperti yang anda ingat, semasa membuat tatasusunan, anda pastinya perlu menentukan objek (atau primitif) yang akan disimpan oleh tatasusunan.

int array[] = new int[10];
Dalam salah satu pelajaran kami sebelum ini, kami memeriksa pemadaman jenis secara terperinci. Dalam kes ini, pemadaman taip menyebabkan kita kehilangan maklumat yang Pairdisimpan oleh objek <String, String>berpasangan. Mencipta tatasusunan akan menjadi tidak selamat. Apabila menggunakan kaedah yang melibatkan varargs dan generik, pastikan anda ingat tentang pemadaman jenis dan cara ia berfungsi. Jika anda benar-benar pasti tentang kod yang anda tulis dan tahu bahawa ia tidak akan menyebabkan sebarang masalah, anda boleh mematikan amaran berkaitan varargs menggunakan anotasi @SafeVarargs.

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

   for (E element : array) {
       list.add(element);
   }
}
Jika anda menambahkan anotasi ini pada kaedah anda, amaran yang kami temui sebelum ini tidak akan muncul. Satu lagi masalah yang boleh berlaku apabila menggunakan varargs dengan generik ialah pencemaran timbunan. Menggunakan varargs apabila bekerja dengan generik - 3Pencemaran timbunan boleh berlaku dalam situasi berikut:

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));
   }
}
Output konsol:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Secara ringkasnya, pencemaran timbunan ialah apabila objek jenis Asepatutnya berada dalam timbunan, tetapi objek jenis Bberakhir di sana disebabkan ralat yang berkaitan dengan keselamatan jenis. Dalam contoh kami, inilah yang berlaku. Mula-mula, kami mencipta pembolehubah mentah numbersdan memberikan koleksi generik ( ArrayList<Number>) kepadanya. Kemudian kami menambah nombor 1pada koleksi.

List<String> strings = numbers;
Pada baris ini, pengkompil cuba memberi amaran kepada kami tentang kemungkinan ralat dengan mengeluarkan amaran " Tugasan tidak disemak... ", tetapi kami mengabaikannya. Kami berakhir dengan pembolehubah generik jenis List<String>yang menunjuk kepada koleksi jenis generik ArrayList<Number>. Jelas sekali, keadaan ini boleh membawa kepada masalah! Dan begitu juga. Menggunakan pembolehubah baharu kami, kami menambah rentetan pada koleksi. Kami kini mempunyai pencemaran timbunan — kami menambahkan nombor dan kemudian rentetan pada koleksi yang diparameterkan. Penyusun memberi amaran kepada kami, tetapi kami tidak mengendahkan amarannya. Akibatnya, kami mendapat ClassCastExceptionhanya semasa program sedang berjalan. Jadi apa kaitannya dengan varargs? Menggunakan varargs dengan generik boleh membawa kepada pencemaran timbunan dengan mudah. Berikut ialah contoh mudah:

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);
   }
}
Apa yang berlaku di sini? Disebabkan pemadaman jenis, hujah pembolehubah panjang kami

List<String>...stringsLists
menjadi tatasusunan senarai, iaitu List[], objek daripada jenis yang tidak diketahui (jangan lupa bahawa varargs bertukar menjadi tatasusunan biasa semasa penyusunan). Oleh sebab itu, kami boleh menetapkannya dengan mudah kepada Object[] arraypembolehubah dalam baris pertama kaedah — jenis objek dalam senarai kami telah dipadamkan! Dan sekarang kita mempunyai Object[]pembolehubah, yang mana kita boleh menambah apa sahaja, kerana semua objek dalam Java mewarisi Object! Pada mulanya, kami hanya mempunyai tatasusunan senarai rentetan. Tetapi terima kasih kepada pemadaman taip dan penggunaan varargs kami, kami boleh menambah senarai nombor dengan mudah, yang kami lakukan. Akibatnya, kami mencemarkan timbunan dengan mencampurkan objek daripada jenis yang berbeza. Hasilnya akan menjadi satu lagi ClassCastExceptionapabila kita cuba membaca rentetan daripada tatasusunan. Output konsol:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Akibat yang tidak dijangka sedemikian boleh disebabkan oleh penggunaan varargs, mekanisme yang kelihatan mudah :) Dan dengan itu, pelajaran hari ini berakhir. Jangan lupa untuk menyelesaikan beberapa tugas, dan jika anda mempunyai masa dan tenaga, pelajari beberapa bacaan tambahan. " Java berkesan " tidak akan membaca sendiri! :) Sehingga lain kali!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION