JavaRush /Blog Java /Random-MS /Menggunakan varargs apabila bekerja dengan generik

Menggunakan varargs apabila bekerja dengan generik

Diterbitkan dalam kumpulan
hello! Dalam pelajaran hari ini kita akan terus mengkaji generik. Kebetulan ini adalah topik besar, tetapi tiada tempat untuk pergi - ini adalah bahagian bahasa yang sangat penting :) Apabila anda mempelajari dokumentasi Oracle mengenai generik atau membaca panduan di Internet, anda akan menemui istilah Jenis Tidak Boleh Ditebus dan Jenis Boleh Ditebus . Apakah jenis perkataan "Boleh Ditegaskan"? Walaupun semuanya baik dengan bahasa Inggeris, anda mungkin tidak pernah menemuinya. Mari cuba menterjemah! Menggunakan varargs apabila bekerja dengan generik - 2
*terima kasih Google, anda banyak membantu -_-*
Jenis reifiable ialah jenis yang maklumatnya tersedia sepenuhnya pada masa jalan. Dalam bahasa Java, ini termasuk jenis primitif, jenis mentah dan bukan generik. Sebaliknya, Jenis Non-Reifiable ialah jenis yang maklumatnya dipadamkan dan tidak tersedia pada masa jalan. Ini hanyalah generik - List<String> , List<Integer> , dsb.

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

Sekiranya anda terlupa, ini adalah argumen panjang berubah-ubah. Mereka berguna dalam situasi di mana kita tidak tahu dengan tepat berapa banyak hujah yang boleh dihantar kepada kaedah kita. Sebagai contoh, jika kita mempunyai kelas kalkulator dan ia mempunyai kaedah sum. sum()Anda boleh lulus 2 nombor, 3, 5, atau seberapa banyak yang anda suka kepada kaedah . Ia akan menjadi sangat pelik untuk membebankan kaedah setiap kali sum()untuk mengambil kira semua pilihan 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
Jadi, penggunaan varargsdalam kombinasi dengan generik mempunyai beberapa ciri penting. Mari lihat kod ini:
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")
       );
   }
}
Kaedah ini mengambil senarai dan sebarang bilangan objek addAll()sebagai input , dan kemudian menambah semua objek ini pada senarai. Dalam kaedah kami memanggil kaedah kami dua kali . Kali pertama kami menambah dalam dua baris biasa. Semuanya baik-baik saja di sini. Kali kedua kami menambah dua objek . Dan di sini kita tiba-tiba mendapat amaran: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
Apakah maksudnya? Mengapakah kita menerima amaran dan apa kaitannya dengannya array? Array- ini ialah tatasusunan, dan tiada tatasusunan dalam kod kami! Mari kita mulakan dengan yang kedua. Amaran menyebut tatasusunan kerana pengkompil menukar argumen panjang berubah-ubah (varargs) kepada tatasusunan. Dengan kata lain, tandatangan kaedah kami ialah addAll():
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 kaedah main(), pengkompil akan 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")
        }
   );
}
Semuanya baik-baik saja dengan tatasusunan String. Tetapi dengan tatasusunan Pair<String, String>- tidak. Hakikatnya ialah Pair<String, String>ini adalah Jenis Tidak Boleh Ditetapkan Semula. Semasa penyusunan, semua maklumat tentang jenis parameter (<String, String>) akan dipadamkan. Mencipta tatasusunan daripada Jenis Tidak Boleh Semula tidak dibenarkan dalam Java . Anda boleh mengesahkan ini jika anda cuba mencipta pasangan tatasusunan<String, String> secara manual
public static void main(String[] args) {

   //  ошибка компиляции! Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Sebabnya jelas - jenis keselamatan. Seperti yang anda ingat, semasa membuat tatasusunan, anda mesti menunjukkan objek (atau primitif) tatasusunan ini akan disimpan.
int array[] = new int[10];
Dalam salah satu pelajaran sebelumnya, kami telah mengkaji secara terperinci mekanisme pemadaman jenis. Jadi, dalam kes ini, akibat pemadaman jenis, kami kehilangan maklumat bahawa Pairpasangan disimpan dalam objek kami <String, String>. Mencipta tatasusunan akan menjadi tidak selamat. Apabila menggunakan kaedah dengan varargsdan generik, pastikan anda ingat tentang pemadaman jenis dan cara ia berfungsi dengan tepat. Jika anda benar-benar yakin dengan kod yang anda tulis, dan anda tahu ia tidak akan menyebabkan sebarang masalah, anda boleh melumpuhkan varargsamaran yang berkaitan dengannya 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 mungkin apabila menggunakan varargsgenerik bersama ialah pencemaran timbunan. Menggunakan varargs apabila bekerja dengan generik - 4Pencemaran boleh berlaku dalam situasi berikut:
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));
   }
}
Output konsol:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Secara ringkasnya, pencemaran timbunan ialah keadaan di mana objek jenis 1 harus berada pada timbunan А, tetapi objek jenis berakhir di sana B, disebabkan ralat keselamatan jenis. Dalam contoh kita inilah yang berlaku. Mula-mula kami mencipta pembolehubah Raw numbersdan memberikannya koleksi generik ArrayList<Number>. Selepas itu kami menambah nombor di sana 1.
List<String> strings = numbers;
Dalam baris ini, pengkompil cuba memberi amaran kepada kami tentang kemungkinan ralat dengan mengeluarkan amaran " Tugasan tidak disemak... ", tetapi kami mengabaikannya. Akibatnya, kami mempunyai pembolehubah generik jenis List<String>, yang menunjuk kepada koleksi generik jenis ArrayList<Number>. Keadaan ini jelas boleh membawa kepada masalah! Inilah yang berlaku. Menggunakan pembolehubah baharu kami, kami menambah rentetan pada koleksi. Timbunan itu telah tercemar - kami menambah dahulu nombor dan kemudian rentetan pada koleksi yang ditaip. Pengkompil memberi amaran kepada kami, tetapi kami tidak mengendahkan amarannya, ClassCastExceptionhanya menerima keputusan semasa program sedang berjalan. Apa kaitannya dengannya varargs? Penggunaan varargsdengan 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 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);
   }
}
Apa yang berlaku di sini? Disebabkan pemadaman jenis, helaian parameter kami (kami akan memanggilnya "helaian" dan bukannya "senarai" untuk kemudahan) ialah -
List<String>...stringsLists
- akan bertukar menjadi tatasusunan helaian - List[]dengan jenis yang tidak diketahui (jangan lupa bahawa varargs bertukar menjadi tatasusunan biasa akibat penyusunan). Oleh sebab itu, kami boleh membuat tugasan kepada pembolehubah Object[] arraydalam baris pertama kaedah dengan mudah - jenis telah dipadamkan daripada helaian kami! Dan kini kita mempunyai pembolehubah jenis Object[], di mana kita boleh menambah apa sahaja - semua objek dalam Java mewarisi daripada Object! Sekarang kita hanya mempunyai tatasusunan helaian rentetan. Tetapi terima kasih kepada penggunaan varargsdan pemadaman jenis, kami boleh menambah helaian nombor padanya dengan mudah, itulah yang kami lakukan. Akibatnya, kami mencemarkan timbunan dengan mencampurkan objek daripada pelbagai jenis. Hasilnya akan menjadi pengecualian yang sama ClassCastExceptionapabila cuba membaca rentetan dari tatasusunan. Output konsol:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Ini adalah akibat yang tidak dijangka yang boleh terhasil daripada menggunakan mekanisme yang kelihatan mudah varargs:) Dan di sinilah kuliah kita hari ini berakhir. Jangan lupa untuk menyelesaikan beberapa masalah, dan jika anda mempunyai masa dan tenaga yang tinggal, pelajari kesusasteraan tambahan. “ Java berkesan ” tidak akan membaca sendiri! :) Jumpa anda!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION